commit 1baa779c49a3c3fd782be5b14e78b712b411554f Author: Jay Date: Sat Aug 12 22:31:33 2017 +0800 first version diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..39fb081 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..7ac24c7 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..3963879 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..e23fdf9 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ 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/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..4af6b23 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,35 @@ +apply plugin: 'com.android.application' + +apply plugin: 'kotlin-android' + +apply plugin: 'kotlin-android-extensions' + +android { + compileSdkVersion 26 + buildToolsVersion "26.0.0" + defaultConfig { + applicationId "xyz.mtfos.btdemo" + minSdkVersion 18 + targetSdkVersion 26 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" + implementation 'com.android.support:appcompat-v7:26.0.0' + implementation 'com.android.support.constraint:constraint-layout:1.0.2' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'com.android.support.test:runner:1.0.0' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.0' + implementation 'org.apache.commons:commons-lang3:3.6' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..e5d4030 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/jay/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# 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/androidTest/java/xyz/mtfos/btdemo/ExampleInstrumentedTest.kt b/app/src/androidTest/java/xyz/mtfos/btdemo/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..462123d --- /dev/null +++ b/app/src/androidTest/java/xyz/mtfos/btdemo/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package xyz.mtfos.btdemo + +import android.support.test.InstrumentationRegistry +import android.support.test.runner.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getTargetContext() + assertEquals("xyz.mtfos.btdemo", appContext.packageName) + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..e6016bb --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/xyz/mtfos/btdemo/BLECls.java b/app/src/main/java/xyz/mtfos/btdemo/BLECls.java new file mode 100644 index 0000000..c6f5f98 --- /dev/null +++ b/app/src/main/java/xyz/mtfos/btdemo/BLECls.java @@ -0,0 +1,315 @@ +package xyz.mtfos.btdemo; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; +import android.bluetooth.BluetoothGattService; +import android.bluetooth.BluetoothManager; +import android.content.Context; +import android.os.Handler; +import android.provider.ContactsContract; +import android.util.Log; + +import org.apache.commons.lang3.ArrayUtils; + +import java.nio.charset.StandardCharsets; +import java.sql.Time; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.UUID; + +/** + * Created by jay on 2017/8/9. + */ + +public class BLECls { + // BLEDemo主要代碼 + private BluetoothAdapter mBtAdapter = null; + private BluetoothGatt mBtGatt = null; + public int mState = 0; + private Context mContext; + private BluetoothGattCharacteristic mWriteCharacteristic = null; + private BluetoothGattCharacteristic mReadCharacteristric = null; + + private final String TAG = "BLE_Demo"; + + // 設備連接狀態 + public final int CONNECTED = 0x01; + public final int DISCONNECTED = 0x02; + public final int CONNECTTING = 0x03; + + private ArrayList> queue = new ArrayList<>(); + + public int ready = 0; + private boolean queueRunning = false; + private boolean scanning = false; + + // ble address + public static final String BLEAddr = "B8:27:EB:21:C2:1A"; + // 讀寫相關的Service、Characteristic的UUID + public static final UUID ServiceUUID = UUID.fromString("dd535b71-8f05-4e30-beb0-6fa7ea3dfc3e"); + public static final UUID TimeUUID = UUID.fromString("00000001-8f05-4e30-beb0-6fa7ea3dfc3e"); + public static final UUID DataUUID = UUID.fromString("00000002-8f05-4e30-beb0-6fa7ea3dfc3e"); + + + // BLE設備連接通信過程中回調 + private BluetoothGattCallback mBtGattCallback = new BluetoothGattCallback() { + + // 連接狀態發生改變時的回調 + @Override + public void onConnectionStateChange(BluetoothGatt gatt, int status, + int newState) { + + if (status == BluetoothGatt.GATT_SUCCESS) { + mState = CONNECTED; + Log.d(TAG, "connected OK"); + mBtGatt.discoverServices(); + } else if (newState == BluetoothGatt.GATT_FAILURE) { + mState = DISCONNECTED; + Log.d(TAG, "connect failed"); + } + } + + // 遠端設備中的服務可用時的回調 + @Override + public void onServicesDiscovered(BluetoothGatt gatt, int status) { + + if (status == BluetoothGatt.GATT_SUCCESS) { + BluetoothGattService btService = mBtGatt.getService(ServiceUUID); + if (btService != null) { + mWriteCharacteristic = btService.getCharacteristic(DataUUID); + mReadCharacteristric = btService.getCharacteristic(TimeUUID); + } + } + } + + // 某Characteristic的狀態為可讀時的回調 + @Override + public void onCharacteristicRead(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic, int status) { + + + } + + // 寫入Characteristic成功與否的回調 + @Override + public void onCharacteristicWrite(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic, int status) { + switch (status) { + case BluetoothGatt.GATT_SUCCESS: + Log.d(TAG, "write data success"); + break;// 寫入成功 + case BluetoothGatt.GATT_FAILURE: + Log.d(TAG, "write data failed"); + break;// 寫入失敗 + case BluetoothGatt.GATT_WRITE_NOT_PERMITTED: + Log.d(TAG, "write not permitted"); + break;// 沒有寫入的權限 + } + queueRunning = false; + runQueue(); + } + + // 訂閱了遠端設備的Characteristic信息後, + // 當遠端設備的Characteristic信息發生改變後,回調此方法 + @Override + public void onCharacteristicChanged(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic) { + readCharacterisricValue(characteristic); + } + + }; + + /** + * 讀取BluetoothGattCharacteristic中的數據 + * + * @param characteristic + */ + private void readCharacterisricValue( + BluetoothGattCharacteristic characteristic) { + byte[] data = characteristic.getValue(); + StringBuffer buffer = new StringBuffer("0x"); + int i; + for (byte b : data) { + i = b & 0xff; + buffer.append(Integer.toHexString(i)); + } + Log.d(TAG, "read data:" + buffer.toString()); + Log.d(TAG, "read data(as string}:" + new String(data)); + } + + /** + * 與指定的設備建立連接 + * + * @param device + */ + public void connect(BluetoothDevice device) { + + mBtGatt = device.connectGatt(mContext, false, mBtGattCallback); + mState = CONNECTTING; + } + + public boolean isScanning() { + return scanning; + } + + public void disconnect() { + if (mBtGatt != null) { + mBtGatt.disconnect(); + } + } + + /** + * 初始化 + * + * @return 如果初始化成功則返回true + */ + public boolean init() { + BluetoothManager btMrg = (BluetoothManager) mContext + .getSystemService(Context.BLUETOOTH_SERVICE); + if (btMrg == null) + return false; + mBtAdapter = btMrg.getAdapter(); + if (mBtAdapter == null) + return false; + return true; + } + + // BLE設備搜索過程中的回調,在此可以根據外圍設備廣播的消息來對設備進行過濾 + private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() { + + @Override + public void onLeScan(final BluetoothDevice device, int rssi, + byte[] scanRecord) { + + ArrayUtils.reverse(scanRecord);// 數組反轉 + // 將Byte數組的數據以十六進製表示並拼接成字符串 + StringBuffer str = new StringBuffer(); + int i = 0; + for (byte b : scanRecord) { + i = (b & 0xff); + str.append(Integer.toHexString(i)); + } + String discoveryServceID = str.toString(); + Log.d(TAG, device.getName() + " scanRecord:\n" + discoveryServceID); + Log.d(TAG, device.getAddress()); + // 查詢是否含有指定的Service UUID信息 +// if (discoveryServceID.indexOf(ServiceUUID.toString().replace("-", "")) != -1) { + if (device.getAddress().equals(BLEAddr)) { + + Log.d(TAG, device.getName() + " has available service UUID"); + + // 在這是處理匹配的設備…… + stopScan(); + connect(device); + } + + } + + }; + + /** + * 開始BLE設備掃瞄 + */ + public void startScan() { + scanning = true; + mBtAdapter.startLeScan(mLeScanCallback); + } + + /** + * 停止BLE設備掃瞄 + */ + public void stopScan() { + scanning = false; + mBtAdapter.stopLeScan(mLeScanCallback); + } + + /** + * 發送數據 + * + * @param data 待發送的數據,最大長度為20 + */ + private void sendData(byte[] data) { + + if (data != null && data.length > 0 && data.length < 21) { + boolean w = mWriteCharacteristic.setValue(data); + boolean s = mBtGatt.writeCharacteristic(mWriteCharacteristic); + System.out.println("send data status ::: " + w + " / " + s); + if (w && s) { + Log.d(TAG, "send data OK"); + } + } + } + + public void sendLongData(byte[] data) { + if (data == null || data.length == 0) return; + Log.d(TAG, "start send data"); + Log.d(TAG, "Data Length :::: " + data.length); + byte[] st = new byte[1]; + Arrays.fill(st, (byte) 0x00); + byte[] ed = new byte[1]; + Arrays.fill(ed, (byte) 0xff); + HashMap a = new HashMap(); + a.put("type", "w"); + a.put("data", st); + pushQueue(a); + + int idx = 0; + + while (true) { + int s = idx * 18; + int e = idx * 18 + 18; + e = e > data.length ? data.length : e; + byte[] tmp = Arrays.copyOfRange(data, s, e); + byte[] tt = new byte[2 + tmp.length]; + tt[1] = (byte) (idx & 0xff); + tt[0] = (byte) ((idx >> 8) & 0xff); + for (int i = 2; i < tt.length; i++) { + if (i - 2 >= tmp.length) break; + tt[i] = tmp[i - 2]; + } + + HashMap thash = new HashMap(); + thash.put("type", "w"); + thash.put("data", tt); + + pushQueue(thash); + idx++; + + if (e >= data.length) break; + } + + HashMap b = new HashMap(); + b.put("type", "w"); + b.put("data", ed); + pushQueue(b); + } + + private void pushQueue(HashMap d) { + if (queue == null) queue = new ArrayList<>(); + queue.add(d); + runQueue(); + } + + private void runQueue() { + if (queue == null) queue = new ArrayList<>(); + if (queue.size() < 1 || queueRunning == true) return; + queueRunning = true; + HashMap tmp = queue.get(0); + queue.remove(0); + if (((String) tmp.get("type")).equals("w")) { + byte[] d = (byte[]) tmp.get("data"); + sendData(d); + } + } + + public BLECls(Context ctx) { + this.mContext = ctx; + } + + +} diff --git a/app/src/main/java/xyz/mtfos/btdemo/MainActivity.kt b/app/src/main/java/xyz/mtfos/btdemo/MainActivity.kt new file mode 100644 index 0000000..010360c --- /dev/null +++ b/app/src/main/java/xyz/mtfos/btdemo/MainActivity.kt @@ -0,0 +1,69 @@ +package xyz.mtfos.btdemo + +import android.app.Activity +import android.os.Bundle +import android.os.Handler +import android.widget.Button +import android.widget.EditText +import xyz.mtfos.btdemo.objectTool.bind + +/** + * Created by jay on 2017/8/9. + */ +class MainActivity : Activity() { + val intxt: EditText by bind(R.id.intxt) + val btn: Button by bind(R.id.bt) + var ble: BLECls? = null + val uiHandler: Handler = Handler() + var th: Thread? = null + var thrun: Boolean = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + btn.isEnabled = false + + ble = BLECls(this@MainActivity) + + if (ble?.init()!!) { + ble?.startScan() + } + + th = Thread(Runnable { -> + {}.run { + thrun = true + while (thrun) { + System.out.println("state ===== " + ble?.mState) + if (ble?.mState == ble?.CONNECTED) { + uiHandler.post { + run { + btn.isEnabled = true + } + } + thrun = false + break + } + Thread.sleep(200) + } + } + }) + th?.start() + + btn.setOnClickListener { + val txt: String = intxt.text.toString() + ble?.sendLongData(txt.toByteArray()) + } + } + + override fun onStop() { + super.onStop() + thrun = false + if (ble?.isScanning!!) { + ble?.stopScan() + } + if (ble?.mState == ble?.CONNECTED) { + ble?.disconnect() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/mtfos/btdemo/objectTool.kt b/app/src/main/java/xyz/mtfos/btdemo/objectTool.kt new file mode 100644 index 0000000..296af0f --- /dev/null +++ b/app/src/main/java/xyz/mtfos/btdemo/objectTool.kt @@ -0,0 +1,23 @@ +package xyz.mtfos.btdemo + +/** + * Created by jay on 2017/8/12. + */ + +import android.app.Activity +import android.support.annotation.IdRes +import android.view.View + +object objectTool { + fun Activity.bind(@IdRes idRes: Int): Lazy { + @Suppress("UNCHECKED_CAST") + return unsafeLazy { findViewById(idRes) } + } + + fun View.bind(@IdRes idRes: Int): Lazy { + @Suppress("UNCHECKED_CAST") + return unsafeLazy { findViewById(idRes) } + } + + private fun unsafeLazy(initializer: () -> T) = lazy(LazyThreadSafetyMode.NONE, initializer) +} \ No newline at end of file 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..1cd2a36 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 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..c8763f4 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,20 @@ + + + + + +