diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fd45b12 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +*.iml +.gradle +/local.properties +/.idea/caches/build_file_checksums.ser +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/.idea/caches/gradle_models.ser b/.idea/caches/gradle_models.ser new file mode 100644 index 0000000..d071ee8 Binary files /dev/null and b/.idea/caches/gradle_models.ser differ diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..681f41a --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,116 @@ + + + + + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
+
+
\ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 0000000..15a15b2 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..88d1552 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..c3c9f49 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..7f68460 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..14f1af3 --- /dev/null +++ b/README.md @@ -0,0 +1,216 @@ +# FastBleGatt + +## 版本更新信息 + +### v1.0.0 + +- 正式版提交 + +## 具体使用: + +### 1,引入库 + +添加JitPack仓库到项目中 + +项目根 build.gradle + +```groovy +allprojects { + repositories { + ... + maven { url 'https://jitpack.io' } + } +} +``` + +添加依赖,app模块中 + +```groovy +dependencies { + implementation 'com.github.shengMR:FastBleGatt:v1.0.0' +} +``` + +### 2,使用 + +1. 初始化蓝牙管理器 + + ```java + FastBleManager.getInstance().init(this); + ``` + +2. 获取一个蓝牙关联器 FastBleGatt,之后所有操作都是对这个蓝牙关联器进行操作 + + ```java + // 参数:device,扫描到的蓝牙BluetoothDevice对象 + FastBleGatt fastBleGatt = FastBleManager.getInstance().with(device); + ``` + +3. 通过蓝牙关联器设置回调并开始连接 + + ```java + FastBleManager.getInstance() + .with(device) + .setBleCallback(new BleCallback() { + @Override + public void onConnecting(BluetoothDevice device) { + // 设备连接中 + } + + @Override + public void onConnected(BluetoothDevice device) { + // 设备连接成功 + } + + @Override + public void onDeviceReady(BluetoothDevice device) { + // 设备准备完毕,此时可以发送命令了 + } + + @Override + public void onDisconnecting(BluetoothDevice device) { + // 设备断开中 + } + + @Override + public void onDisconnectByUser(BluetoothDevice device) { + // 设备主动断开 + } + + @Override + public void onDisconnected(BluetoothDevice device) { + // 设备被动断开 + } + + @Override + public void onConnectTimeout(BluetoothDevice device) { + // 设备连接超时 + } + }) + .connect(); + ``` + +4. 通过蓝牙关联器对指定蓝牙设备进行操作 + + - 开启通知 + + ```java + // 创建一条命令 + Request request = Request.newEnableNotifyRequest( + serviceUUID, // 服务UUID + characteristicUuid, // 特征UUID + new RequestCallback() { // 请求命令回调 + @Override + public void success(FastBleGatt fastBleGatt, Request request, Object data) { + // 发送成功 + } + + @Override + public void error(FastBleGatt fastBleGatt, Request request, String errorMsg) { + // 发送失败 + } + + @Override + public boolean timeout(FastBleGatt fastBleGatt, Request request) { + // 发送超时,如果返回false,则不再继续发送,返回true则重新发送此命令 + return false; + } + }); + + // 对指定设备做操作 + FastBleManager.getInstance() + .with(device) + .sendRequest(request); + ``` + + - 写操作 + + ```java + // 创建一条命令 + Request request = Request.newReadRequest( + serviceUUID, // 服务UUID + characteristicUuid, // 特征UUID + new RequestCallback() { // 请求命令回调 + @Override + public void success(FastBleGatt fastBleGatt, Request request, Object data) { + // 发送成功 + } + + @Override + public void error(FastBleGatt fastBleGatt, Request request, String errorMsg) { + // 发送失败 + } + + @Override + public boolean timeout(FastBleGatt fastBleGatt, Request request) { + // 发送超时,如果返回false,则不再继续发送,返回true则重新发送此命令 + return false; + } + }); + + // 对指定设备做操作 + FastBleManager.getInstance() + .with(device).sendRequest(request); + ``` + + - 读操作 + + ```java + // 创建一条命令 + Request request = Request.newReadRequest( + serviceUUID, // 服务UUID + characteristicUuid, // 特征UUID + data, // 需要写的数据 + new RequestCallback() { // 请求命令回调 + @Override + public void success(FastBleGatt fastBleGatt, Request request, Object data) { + // 发送成功 + } + + @Override + public void error(FastBleGatt fastBleGatt, Request request, String errorMsg) { + // 发送失败 + } + + @Override + public boolean timeout(FastBleGatt fastBleGatt, Request request) { + // 发送超时,如果返回false,则不再继续发送,返回true则重新发送此命令 + return false; + } + }); + + // 对指定设备做操作 + FastBleManager.getInstance() + .with(device).sendRequest(request); + ``` + +## 基本概念 + +### 1,Request消息对象 + +​ 我们要发送一个消息,需要创建一个请求对象,即Request,可用类方法直接构造出来,然后通过蓝牙关联器对指定设备进行发送 + +```java +Request.newWriteRequest(); // 写操作 +Request.newReadRequest(); // 读操作 +Request.newMtuRequest(); // 申请MTU操作 +Request.newEnableNotifyRequest(); // 开启通知 +Request.newDisableNotifyRequest(); // 关闭通知 +``` + +每个Request消息都会要求输入一个RequestCallback对象,用来反馈消息的发送成功与否 + +#### 消息特性: + +* 延时发送 + + 每一个Request对象都可以设置延时的时间,不过延时的时间相对于上一条命令 + + 如:request(1)延时200ms,request(2)延时300ms,然后两条一起发送,命令执行如下: + + sendRequest ---- 间隔200ms ---- request(1)发送 ---- 成功/失败 ---- 延时300ms --- request(2)发送 + +* 消息Tag + + 每一个Request对象都可以携带一个Tag变量,用来发送成功失败判断是哪一个包的失败与发送 + diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..822343f --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 28 + defaultConfig { + applicationId "com.cys.fastblegatt" + minSdkVersion 19 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'androidx.appcompat:appcompat:1.0.2' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + implementation 'androidx.recyclerview:recyclerview:1.0.0' + implementation 'com.google.code.gson:gson:2.8.5' + + implementation 'com.github.shengMR:FastBleScan:master-SNAPSHOT' + implementation 'com.github.shengMR:BasisUtil:v1.0.1' + implementation project(path: ':fastblegatt') +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..ff27d7f --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/cys/DiscoverServicesActivity.java b/app/src/main/java/com/cys/DiscoverServicesActivity.java new file mode 100644 index 0000000..5625045 --- /dev/null +++ b/app/src/main/java/com/cys/DiscoverServicesActivity.java @@ -0,0 +1,348 @@ +package com.cys; + +import android.annotation.SuppressLint; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattService; +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseExpandableListAdapter; +import android.widget.Button; +import android.widget.ExpandableListView; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; + +import com.cys.basis.view.helper.adapter.BaseViewHolder; +import com.cys.fastblegatt.FastBleGatt; +import com.cys.fastblegatt.FastBleManager; +import com.cys.fastblegatt.callback.BleCallback; +import com.cys.fastblegatt.callback.RequestCallback; +import com.cys.fastblegatt.request.Request; +import com.cys.fastblegatt.util.Logger; +import com.cys.fastblegatt.util.PrintHelpper; +import com.cys.fastblescan.bean.ScanDevice; + +import java.util.ArrayList; +import java.util.List; + +public class DiscoverServicesActivity extends AppCompatActivity { + + private ExpandableListView idElvService; + private ElvAdapter adapterService; + private FastBleGatt currentGatt; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_discover_services); + + + if (getIntent().hasExtra("CurrentDevice")) { + final ScanDevice scanDevice = getIntent().getParcelableExtra("CurrentDevice"); + currentGatt = FastBleManager.getInstance().with(scanDevice.device); + currentGatt + .setBleCallback(new BleCallback() { + @Override + public void onConnecting(BluetoothDevice device) { + Logger.d("连接中 = " + device.getAddress()); + } + + @Override + public void onConnected(BluetoothDevice device) { + Logger.d("连接成功 = " + device.getAddress()); + } + + @Override + public void onDeviceReady(BluetoothDevice device) { + Logger.d("设备准备完毕 = " + device.getAddress()); + runOnUiThread(new Runnable() { + @Override + public void run() { + if (currentGatt == null) { + return; + } + if (adapterService != null) { + adapterService.replaceData(currentGatt.getGattServices()); + } + } + }); + } + + @Override + public void onDisconnecting(BluetoothDevice device) { + Logger.d("设备断开中 = " + device.getAddress()); + } + + @Override + public void onDisconnectByUser(BluetoothDevice device) { + Logger.d("设备主动断开 = " + device.getAddress()); + } + + @Override + public void onDisconnected(BluetoothDevice device) { + Logger.d("设备被动断开 = " + device.getAddress()); + finish(); + } + + @Override + public void onConnectTimeout(BluetoothDevice device) { + finish(); + } + }) + .connect(); + } + + idElvService = findViewById(R.id.id_elv); + idElvService.setGroupIndicator(null); + adapterService = new ElvAdapter(this); + idElvService.setAdapter(adapterService); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (currentGatt != null) { + currentGatt.disconnect(); + } + } + + class ElvAdapter extends BaseExpandableListAdapter { + + public Context mContext; + public LayoutInflater mInflater; + public List mService = new ArrayList<>(); + + public void replaceData(List services) { + if (mService != services) { + mService.clear(); + mService.addAll(services); + } + notifyDataSetChanged(); + } + + public ElvAdapter(Context context) { + this.mContext = context.getApplicationContext(); + this.mInflater = LayoutInflater.from(this.mContext); + } + + @Override + public int getGroupCount() { + return mService.size(); + } + + @Override + public int getChildrenCount(int groupPosition) { + return mService.get(groupPosition).getCharacteristics().size(); + } + + @Override + public BluetoothGattService getGroup(int groupPosition) { + return mService.get(groupPosition); + } + + @Override + public BluetoothGattCharacteristic getChild(int groupPosition, int childPosition) { + return mService.get(groupPosition).getCharacteristics().get(childPosition); + } + + @Override + public long getGroupId(int groupPosition) { + return groupPosition; + } + + @Override + public long getChildId(int groupPosition, int childPosition) { + return childPosition; + } + + @Override + public boolean hasStableIds() { + return false; + } + + @Override + public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { + BaseViewHolder viewHolder = BaseViewHolder.getViewHolder(this.mContext, R.layout.adapter_item_service, groupPosition, convertView, parent); + viewHolder.setText(R.id.id_tv_service_uuid, "服务 -> " + adapterService.mService.get(groupPosition).getUuid().toString()); + if (isExpanded) { + viewHolder.setImageResource(R.id.id_iv_arrow, R.drawable.ic_arrow_down); + } else { + viewHolder.setImageResource(R.id.id_iv_arrow, R.drawable.ic_arrow_up); + } + return viewHolder.getItemView(); + } + + @SuppressLint("SetTextI18n") + @Override + public View getChildView(final int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { + final BluetoothGattCharacteristic bean = adapterService.mService.get(groupPosition).getCharacteristics().get(childPosition); + BaseViewHolder viewHolder = BaseViewHolder.getViewHolder(this.mContext, R.layout.adapter_item_characteristic, childPosition, convertView, parent); + viewHolder.setText(R.id.id_tv_characteritic_uuid, "特征 -> " + bean.getUuid().toString()); + TextView readTv = viewHolder.getView(R.id.id_tv_read); + Button readBt = viewHolder.getView(R.id.id_read); + Button writeBt = viewHolder.getView(R.id.id_write); + Button writeNRBt = viewHolder.getView(R.id.id_write_no_response); + Button notifyBt = viewHolder.getView(R.id.id_notify); + readTv.setVisibility(View.GONE); + readBt.setVisibility(View.GONE); + writeBt.setVisibility(View.GONE); + writeNRBt.setVisibility(View.GONE); + notifyBt.setVisibility(View.GONE); + + // 读按钮 + readBt.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Request request = Request.newReadRequest(adapterService.mService.get(groupPosition).getUuid(), bean.getUuid(), new RequestCallback() { + @Override + public void success(FastBleGatt baseFastGatt, Request request, Object data) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(DiscoverServicesActivity.this, "读取成功", Toast.LENGTH_SHORT).show(); + adapterService.notifyDataSetChanged(); + } + }); + } + + @Override + public void error(FastBleGatt baseFastGatt, Request request, String errorMsg) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(DiscoverServicesActivity.this, "读取失败", Toast.LENGTH_SHORT).show(); + } + }); + } + + @Override + public boolean timeout(FastBleGatt baseFastGatt, Request request) { + return false; + } + }); + currentGatt.sendRequest(request); + } + }); + + writeBt.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + byte[] data = new byte[]{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x00/*, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06*/ + }; + List requests = FastBleManager.getInstance().calculRequest( + adapterService.mService.get(groupPosition).getUuid(), + bean.getUuid(), + data, + currentGatt.getBleDataSize(), + new RequestCallback() { + @Override + public void success(FastBleGatt baseFastGatt, Request request, Object data) { + Logger.d("发送成功:" + "Tag = " + request.tag + " " + PrintHelpper.bytesToHexString(request.data, ",")); + } + + @Override + public void error(FastBleGatt baseFastGatt, Request request, String errorMsg) { + + } + + @Override + public boolean timeout(FastBleGatt baseFastGatt, Request request) { + return false; + } + }); + Logger.d("request length = " + requests.size()); + for (int i = 0; i < requests.size(); i++) { + Logger.d(String.format("request data[%d] = %s", i, PrintHelpper.bytesToHexString(requests.get(i).data, ","))); + } + + currentGatt.sendRequests(requests); + } + }); + + // 通知按钮 + notifyBt.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Request request = Request.newEnableNotifyRequest(adapterService.mService.get(groupPosition).getUuid(), bean.getUuid(), new RequestCallback() { + @Override + public void success(FastBleGatt baseFastGatt, Request request, Object data) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(DiscoverServicesActivity.this, "成功", Toast.LENGTH_SHORT).show(); + } + }); + } + + @Override + public void error(FastBleGatt baseFastGatt, Request request, String errorMsg) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(DiscoverServicesActivity.this, "失败", Toast.LENGTH_SHORT).show(); + } + }); + } + + @Override + public boolean timeout(FastBleGatt baseFastGatt, Request request) { + return false; + } + }); + currentGatt.sendRequest(request); + } + }); + + + // 解析属性 + int properties = bean.getProperties(); + if ((properties & BluetoothGattCharacteristic.PROPERTY_BROADCAST) == BluetoothGattCharacteristic.PROPERTY_BROADCAST) { + + } + if ((properties & BluetoothGattCharacteristic.PROPERTY_READ) == BluetoothGattCharacteristic.PROPERTY_READ) { + readBt.setVisibility(View.VISIBLE); + if (bean.getValue() != null) { + readTv.setVisibility(View.VISIBLE); + + readTv.setText("[" + PrintHelpper.bytesToHexString(bean.getValue(), ",") + "]" + new String(bean.getValue())); + } + } + if ((properties & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) == BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) { + writeNRBt.setVisibility(View.VISIBLE); + } + if ((properties & BluetoothGattCharacteristic.PROPERTY_WRITE) == BluetoothGattCharacteristic.PROPERTY_WRITE) { + writeBt.setVisibility(View.VISIBLE); + } + if ((properties & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) { + notifyBt.setVisibility(View.VISIBLE); + } + if ((properties & BluetoothGattCharacteristic.PROPERTY_INDICATE) == BluetoothGattCharacteristic.PROPERTY_INDICATE) { + + } + if ((properties & BluetoothGattCharacteristic.PROPERTY_SIGNED_WRITE) == BluetoothGattCharacteristic.PROPERTY_SIGNED_WRITE) { + + } + if ((properties & BluetoothGattCharacteristic.PROPERTY_EXTENDED_PROPS) == BluetoothGattCharacteristic.PROPERTY_EXTENDED_PROPS) { + + } + + + return viewHolder.getItemView(); + } + + @Override + public boolean isChildSelectable(int groupPosition, int childPosition) { + return false; + } + } +} diff --git a/app/src/main/java/com/cys/MainActivity.java b/app/src/main/java/com/cys/MainActivity.java new file mode 100644 index 0000000..f2ca8d5 --- /dev/null +++ b/app/src/main/java/com/cys/MainActivity.java @@ -0,0 +1,245 @@ +package com.cys; + +import android.Manifest; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.cys.adapter.DeviceAdapter; +import com.cys.fastblegatt.FastBleManager; +import com.cys.fastblegatt.util.Logger; +import com.cys.fastblescan.FastBleScanner; +import com.cys.fastblescan.bean.ScanDevice; +import com.cys.fastblescan.callback.FastBleScanCallback; + +import java.util.ArrayList; +import java.util.List; + +public class MainActivity extends AppCompatActivity implements View.OnClickListener, + FastBleScanCallback, DeviceAdapter.OnItemClickListener { + + private RecyclerView idRcvDevice; + private List mDeviceList = new ArrayList<>(); + private DeviceAdapter adapterDevice; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + Logger.isDebug = true; + + if (!FastBleScanner.getInstance().isSupportBluetooth()) { + Toast.makeText(this, "本设备不支持蓝牙", Toast.LENGTH_SHORT).show(); + finish(); + return; + } + + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 1001); + } + + findViewById(R.id.id_bt_start_scan).setOnClickListener(this); + findViewById(R.id.id_bt_stop_scan).setOnClickListener(this); + findViewById(R.id.id_bt_disconnect).setOnClickListener(this); + + idRcvDevice = findViewById(R.id.id_rcv_device); + idRcvDevice.setLayoutManager(new LinearLayoutManager(this)); + idRcvDevice.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL)); + adapterDevice = new DeviceAdapter(); + idRcvDevice.setAdapter(adapterDevice); + adapterDevice.setOnItemClickListener(this); + + FastBleScanner.getInstance().setScanCallback(this); + FastBleManager.getInstance().init(this); + } + + @Override + protected void onResume() { + super.onResume(); + if (!FastBleScanner.getInstance().isBluetoothEnabled()) { + FastBleScanner.getInstance().enableBluetooth(); + } + } + + @Override + public void onClick(View v) { + if (v.getId() == R.id.id_bt_start_scan) { + if (FastBleScanner.getInstance().isBluetoothEnabled()) { + mDeviceList.clear(); + adapterDevice.replaceData(mDeviceList); + FastBleScanner.getInstance().setIgnoreSame(true).startScan(); + } + } else if (v.getId() == R.id.id_bt_stop_scan) { + if (FastBleScanner.getInstance().isBluetoothEnabled()) { + FastBleScanner.getInstance().stopScan(); + } + } else if (v.getId() == R.id.id_bt_disconnect) { + + } + } + + @Override + public void onStartScan() { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(MainActivity.this, "开始扫描", Toast.LENGTH_SHORT).show(); + } + }); + } + + @Override + public void onLeScan(final ScanDevice scanDevice) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Log.d("Sheng", "扫描 -> " + scanDevice.deviceName); + mDeviceList.add(scanDevice); + adapterDevice.replaceData(mDeviceList); + } + }); + } + + @Override + public void onStopScan() { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(MainActivity.this, "停止扫描", Toast.LENGTH_SHORT).show(); + } + }); + } + + @Override + public void onScanFailure(int errorCode) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(MainActivity.this, "扫描出错", Toast.LENGTH_SHORT).show(); + } + }); + } + + @Override + public void onItemClick(ScanDevice scanDevice) { + if (FastBleScanner.getInstance().isBluetoothEnabled()) { + FastBleScanner.getInstance().stopScan(); + } + Intent intent = new Intent(this, DiscoverServicesActivity.class); + intent.putExtra("CurrentDevice", scanDevice); + startActivity(intent); + } + + /* + public void onDeviceReady() { + Request requestEnableNotify = Request.newWriteRequest(mServiceUuid, mCharactiesticUuid, new byte[]{0x01}, new RequestCallback() { + @Override + public void success(BaseFastBleGatt baseFastGatt, Request request, Object data) { + mLogger.d("开启通知 发送成功"); + } + + @Override + public void error(BaseFastBleGatt baseFastGatt, Request request, String errorMsg) { + mLogger.d("开启通知 发送失败"); + } + + @Override + public boolean timeout(BaseFastBleGatt baseFastGatt, Request request) { + mLogger.d("开启通知 发送超时"); + return false; + } + }); + + mFastGatt.sendRequest(requestEnableNotify); + + Request request2EnableNotify = Request.newWriteRequest(mServiceUuid, mCharactiesticUuid, new byte[]{0x01}, new RequestCallback() { + @Override + public void success(BaseFastBleGatt baseFastGatt, Request request, Object data) { + mLogger.d("开启通知 发送成功"); + } + + @Override + public void error(BaseFastBleGatt baseFastGatt, Request request, String errorMsg) { + mLogger.d("开启通知 发送失败"); + } + + @Override + public boolean timeout(BaseFastBleGatt baseFastGatt, Request request) { + mLogger.d("开启通知 发送超时"); + return false; + } + }, 4000); + + mFastGatt.sendRequest(request2EnableNotify); + + for (int i = 513; i < 530; i ++) { + Request requestMtu = Request.newMtuRequest( + UUID.fromString("00010203-0405-0607-0809-0a0b0c0d1910"), + UUID.fromString("00010203-0405-0607-0809-0a0b0c0d1911"), i, new RequestCallback() { + @Override + public void success(BaseFastBleGatt baseFastGatt, Request request, Object data) { + Logger.d("Mtu 发送成功" + (int) request.getTag()); + } + + @Override + public void error(BaseFastBleGatt baseFastGatt, Request request, String errorMsg) { + Logger.d("Mtu 发送失败"); + } + + @Override + public boolean timeout(BaseFastBleGatt baseFastGatt, Request request) { + Logger.d("Mtu 发送超时" + (int) request.getTag()); + return false; + } + }); + requestMtu.setTag(i); + FastBleGatt.getInstance().sendRequest(requestMtu); + } + + byte[] data = new byte[]{ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 + }; + List requests = FastBleGatt.getInstance().calculData( + UUID.fromString("00010203-0405-0607-0809-0a0b0c0d1910"), + UUID.fromString("00010203-0405-0607-0809-0a0b0c0d1911"), + data, new RequestCallback() { + @Override + public void success(BaseFastBleGatt baseFastGatt, Request request, Object data) { + Logger.d("发送成功" + ArraysUtils.bytesToHexString(request.data, ",")); + } + + @Override + public void error(BaseFastBleGatt baseFastGatt, Request request, String errorMsg) { + + } + + @Override + public boolean timeout(BaseFastBleGatt baseFastGatt, Request request) { + return false; + } + }); + Logger.d("request length = " + requests.size()); + for (int i = 0; i < requests.size(); i++) { + Logger.d(String.format("request data[%d] = %s", i, ArraysUtils.bytesToHexString(requests.get(i).data, ","))); + } + + FastBleGatt.getInstance().sendCalcuData(requests); + + } + */ + +} diff --git a/app/src/main/java/com/cys/adapter/DeviceAdapter.java b/app/src/main/java/com/cys/adapter/DeviceAdapter.java new file mode 100644 index 0000000..a62bc61 --- /dev/null +++ b/app/src/main/java/com/cys/adapter/DeviceAdapter.java @@ -0,0 +1,66 @@ +package com.cys.adapter; + +import android.bluetooth.BluetoothDevice; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.cys.R; +import com.cys.fastblescan.bean.ScanDevice; + +import java.util.ArrayList; +import java.util.List; + +public class DeviceAdapter extends RecyclerView.Adapter { + + public List mDatas = new ArrayList<>(); + + public interface OnItemClickListener{ + void onItemClick(ScanDevice scanDevice); + } + + public OnItemClickListener mItemClickListener; + + public void setOnItemClickListener(OnItemClickListener clickListener){ + this.mItemClickListener = clickListener; + } + + public void replaceData(List datas) { + this.mDatas.clear(); + this.mDatas.addAll(datas); + notifyDataSetChanged(); + } + + @NonNull + @Override + public DeviceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View inflate = LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_item_device, parent, false); + return new DeviceViewHolder(inflate); + } + + @Override + public void onBindViewHolder(@NonNull DeviceViewHolder holder, int position) { + final ScanDevice scanDevice = mDatas.get(position); + BluetoothDevice device = scanDevice.device; + holder.textViewByName.setText((TextUtils.isEmpty(scanDevice.deviceName) ? device.getAddress() : scanDevice.deviceName) + + "( rssi " + scanDevice.rssi + " ) "); + holder.textViewByMac.setText("Mac : " + device.getAddress()); + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if(mItemClickListener != null){ + mItemClickListener.onItemClick(scanDevice); + } + } + }); + } + + @Override + public int getItemCount() { + return mDatas.size(); + } +} diff --git a/app/src/main/java/com/cys/adapter/DeviceViewHolder.java b/app/src/main/java/com/cys/adapter/DeviceViewHolder.java new file mode 100644 index 0000000..785d7ad --- /dev/null +++ b/app/src/main/java/com/cys/adapter/DeviceViewHolder.java @@ -0,0 +1,20 @@ +package com.cys.adapter; + +import androidx.recyclerview.widget.RecyclerView; +import android.view.View; +import android.widget.TextView; + +import com.cys.R; + + +public class DeviceViewHolder extends RecyclerView.ViewHolder { + + public TextView textViewByName; + public TextView textViewByMac; + + public DeviceViewHolder(View itemView) { + super(itemView); + textViewByName = itemView.findViewById(R.id.id_tv_name); + textViewByMac = itemView.findViewById(R.id.id_tv_mac); + } +} diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..1f6bb29 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable-xxhdpi/ic_arrow_down.png b/app/src/main/res/drawable-xxhdpi/ic_arrow_down.png new file mode 100644 index 0000000..08e107f Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_arrow_down.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_arrow_up.png b/app/src/main/res/drawable-xxhdpi/ic_arrow_up.png new file mode 100644 index 0000000..3911c26 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_arrow_up.png differ diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..0d025f9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_discover_services.xml b/app/src/main/res/layout/activity_discover_services.xml new file mode 100644 index 0000000..c445564 --- /dev/null +++ b/app/src/main/res/layout/activity_discover_services.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..0bb9f33 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,50 @@ + + + +