Ver código fonte

蓝牙配网功能,该盒子作为ble服务端,对端app作为ble客户端,通过uuid匹配扫描到盒子后进行连接和数据传输
目前只完成双向通信,具体的配网操作待处理

wzl 11 meses atrás
pai
commit
d75e3f6f49

+ 7 - 1
android_bed/src/main/AndroidManifest.xml

@@ -24,6 +24,10 @@
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
     <uses-permission android:name="android.permission.KILL_BACKGROUND_PROCESSES" />
 
 
+    <!-- required = true 表示手机必须支持BLE,否则无法安装运行-->
+    <uses-feature
+        android:name="android.hardware.bluetooth_le"
+        android:required="false" />
 
 
     <uses-feature
     <uses-feature
         android:name="android.hardware.nfc"
         android:name="android.hardware.nfc"
@@ -89,7 +93,9 @@
 
 
         <service android:name="com.wdkl.app.ncs.callingbed.sleep.SleepService"/>
         <service android:name="com.wdkl.app.ncs.callingbed.sleep.SleepService"/>
 
 
-        <service   android:name="com.wdkl.app.ncs.callingbed.service.ViewService"/>
+        <service android:name="com.wdkl.app.ncs.callingbed.service.ViewService"/>
+
+        <service android:name="com.wdkl.app.ncs.callingbed.service.BleService"/>
 
 
         <receiver
         <receiver
             android:name="com.wdkl.app.ncs.callingbed.broadcast.WdBootReceiver"
             android:name="com.wdkl.app.ncs.callingbed.broadcast.WdBootReceiver"

+ 21 - 4
android_bed/src/main/java/com/wdkl/app/ncs/callingbed/activity/CallingbedActivationActivity.kt

@@ -1,12 +1,18 @@
 package com.wdkl.app.ncs.callingbed.activity
 package com.wdkl.app.ncs.callingbed.activity
 
 
 import android.app.zhyl.ZhylManager
 import android.app.zhyl.ZhylManager
+import android.bluetooth.*
+import android.bluetooth.le.AdvertiseCallback
+import android.bluetooth.le.AdvertiseData
+import android.bluetooth.le.AdvertiseSettings
+import android.bluetooth.le.BluetoothLeAdvertiser
 import android.content.Intent
 import android.content.Intent
-import android.net.Uri
+import android.content.pm.PackageManager
 import android.net.wifi.WifiManager
 import android.net.wifi.WifiManager
 import android.os.Build
 import android.os.Build
 import android.os.Handler
 import android.os.Handler
 import android.os.Looper
 import android.os.Looper
+import android.os.ParcelUuid
 import android.provider.Settings
 import android.provider.Settings
 import android.text.TextUtils
 import android.text.TextUtils
 import android.util.Log
 import android.util.Log
@@ -18,12 +24,14 @@ import com.enation.javashop.utils.base.config.BaseConfig
 import com.google.gson.Gson
 import com.google.gson.Gson
 import com.wdkl.app.ncs.callingbed.BuildConfig
 import com.wdkl.app.ncs.callingbed.BuildConfig
 import com.wdkl.app.ncs.callingbed.R
 import com.wdkl.app.ncs.callingbed.R
+import com.wdkl.app.ncs.callingbed.ble.BLE_Constants
 import com.wdkl.app.ncs.callingbed.databinding.CallingbedActivationBinding
 import com.wdkl.app.ncs.callingbed.databinding.CallingbedActivationBinding
 import com.wdkl.app.ncs.callingbed.dialog.ServicesDialogHelper
 import com.wdkl.app.ncs.callingbed.dialog.ServicesDialogHelper
 import com.wdkl.app.ncs.callingbed.dialog.SystemDialogHelper
 import com.wdkl.app.ncs.callingbed.dialog.SystemDialogHelper
 import com.wdkl.app.ncs.callingbed.hardware.HardWareFactory
 import com.wdkl.app.ncs.callingbed.hardware.HardWareFactory
 import com.wdkl.app.ncs.callingbed.helper.*
 import com.wdkl.app.ncs.callingbed.helper.*
 import com.wdkl.app.ncs.callingbed.launch.CallingbedLaunch
 import com.wdkl.app.ncs.callingbed.launch.CallingbedLaunch
+import com.wdkl.app.ncs.callingbed.service.BleService
 import com.wdkl.app.ncs.callingbed.service.ViewService
 import com.wdkl.app.ncs.callingbed.service.ViewService
 import com.wdkl.app.ncs.callingbed.settings.SettingConfig
 import com.wdkl.app.ncs.callingbed.settings.SettingConfig
 import com.wdkl.app.ncs.callingbed.utils.SPUtils
 import com.wdkl.app.ncs.callingbed.utils.SPUtils
@@ -68,9 +76,7 @@ class CallingbedActivationActivity  : BaseActivity<CallingbedActivationPresenter
     private var clickTime: Long = 0
     private var clickTime: Long = 0
     private var clickTimes: Int = 1
     private var clickTimes: Int = 1
 
 
-
-
-     var viewService: ViewService? = null
+    var viewService: ViewService? = null
 
 
     override fun getLayId(): Int {
     override fun getLayId(): Int {
 
 
@@ -106,6 +112,15 @@ class CallingbedActivationActivity  : BaseActivity<CallingbedActivationPresenter
         XCrashUtils().init(application)
         XCrashUtils().init(application)
 
 
 
 
+        //蓝牙配网服务
+        val bleIntent = Intent(this, BleService::class.java)
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            //android8.0以上通过startForegroundService启动service
+            startForegroundService(bleIntent)
+        } else {
+            startService(bleIntent)
+        }
+
         checkServer()
         checkServer()
     }
     }
 
 
@@ -184,6 +199,8 @@ class CallingbedActivationActivity  : BaseActivity<CallingbedActivationPresenter
         serverSuccess = true
         serverSuccess = true
         val serviceIntent = Intent(this, ViewService::class.java)
         val serviceIntent = Intent(this, ViewService::class.java)
         stopService(serviceIntent)
         stopService(serviceIntent)
+
+        handler.removeCallbacksAndMessages(null)
     }
     }
 
 
     private fun showUI(){
     private fun showUI(){

+ 11 - 0
android_bed/src/main/java/com/wdkl/app/ncs/callingbed/ble/BLE_Constants.java

@@ -0,0 +1,11 @@
+package com.wdkl.app.ncs.callingbed.ble;
+
+import java.util.UUID;
+
+public class BLE_Constants {
+    public static final UUID UUID_SERVICE = UUID.fromString("10000000-00EE-0000-0CBD-000000000001"); //自定义UUID
+    public static final UUID UUID_CHAR_READ_NOTIFY = UUID.fromString("11000000-00EE-0000-0CBD-000000000002");
+    public static final UUID UUID_DESC_NOTITY = UUID.fromString("11100000-00EE-0000-0CBD-000000000003");
+    public static final UUID UUID_CHAR_WRITE = UUID.fromString("12000000-00EE-0000-0CBD-000000000004");
+
+}

+ 4 - 0
android_bed/src/main/java/com/wdkl/app/ncs/callingbed/helper/AppUpdateHelper.java

@@ -15,6 +15,7 @@ import android.os.Environment;
 import android.util.Log;
 import android.util.Log;
 
 
 import com.wdkl.app.ncs.callingbed.BuildConfig;
 import com.wdkl.app.ncs.callingbed.BuildConfig;
+import com.wdkl.app.ncs.callingbed.service.BleService;
 import com.wdkl.ncs.android.component.welcome.activity.WelcomeActivity;
 import com.wdkl.ncs.android.component.welcome.activity.WelcomeActivity;
 import com.wdkl.ncs.android.middleware.common.Constant;
 import com.wdkl.ncs.android.middleware.common.Constant;
 
 
@@ -286,6 +287,9 @@ public class AppUpdateHelper {
     }
     }
 
 
     public static void restartApp(Context context) {
     public static void restartApp(Context context) {
+        Intent serviceIntent = new Intent(context, BleService.class);
+        context.stopService(serviceIntent);
+
         //重新启动app
         //重新启动app
         Intent mStartActivity = new Intent(context.getApplicationContext(), WelcomeActivity.class);
         Intent mStartActivity = new Intent(context.getApplicationContext(), WelcomeActivity.class);
         mStartActivity.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
         mStartActivity.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

+ 334 - 0
android_bed/src/main/java/com/wdkl/app/ncs/callingbed/service/BleService.kt

@@ -0,0 +1,334 @@
+package com.wdkl.app.ncs.callingbed.service
+
+import android.app.*
+import android.bluetooth.*
+import android.bluetooth.le.AdvertiseCallback
+import android.bluetooth.le.AdvertiseData
+import android.bluetooth.le.AdvertiseSettings
+import android.bluetooth.le.BluetoothLeAdvertiser
+import android.content.Context
+import android.content.Intent
+import android.os.*
+import android.util.Log
+import com.wdkl.app.ncs.callingbed.R
+import com.wdkl.app.ncs.callingbed.ble.BLE_Constants
+import com.wdkl.ncs.android.lib.utils.showMessage
+import java.util.*
+
+class BleService : Service() {
+    private val TAG = BleService::class.java.simpleName
+
+    internal var myBinder = ServiceBinder()
+
+    private var notificationManager: NotificationManager? = null
+    private var notificationId: String = "channelId0"
+    private var notificationName: String = "channelName"
+
+    private val bleHandler by lazy { Handler(Looper.getMainLooper()) }
+
+    //蓝牙ble配网相关,只有在设备激活页面有效,如果已经正常进入主页面则认为已经正常联网
+    private var mBluetoothLeAdvertiser: BluetoothLeAdvertiser? = null // BLE广播
+    private var mBluetoothGattServer: BluetoothGattServer? = null // BLE服务端
+
+    override fun onCreate() {
+        super.onCreate()
+
+        notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager?
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            val channel = NotificationChannel(
+                    notificationId,
+                    notificationName,
+                    NotificationManager.IMPORTANCE_HIGH
+            )
+            channel.enableVibration(true)
+            channel.enableLights(true)
+            channel.setBypassDnd(true)
+            channel.setShowBadge(true)
+            channel.setSound(null, null)
+            notificationManager!!.createNotificationChannel(channel)
+        }
+
+
+        bleHandler.postDelayed({
+            configBle()
+        }, 15000)
+    }
+
+    private fun getNotification(): Notification {
+        val builder: Notification.Builder = Notification.Builder(this)
+            .setSmallIcon(R.drawable.ic_launch_48)
+            .setContentTitle("Ble service")
+            .setContentText("running...")
+            .setOnlyAlertOnce(true)
+
+        //设置Notification的ChannelID,否则不能正常显示
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            builder.setChannelId(notificationId)
+        }
+        val notification: Notification = builder.build()
+        return notification
+    }
+
+    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            startForeground(110, getNotification())//开前台服务
+        }
+
+        Log.i(TAG, "onStartCommand:")
+        //return START_STICKY//当服务被异常终止时,重启服务
+        return super.onStartCommand(intent, flags, startId)
+    }
+
+    override fun onBind(intent: Intent): IBinder {
+        return myBinder
+    }
+
+    override fun onDestroy() {
+        super.onDestroy()
+
+        bleHandler.removeCallbacksAndMessages(null)
+        if (mBluetoothLeAdvertiser != null) {
+            mBluetoothLeAdvertiser!!.stopAdvertising(mAdvertiseCallback)
+        }
+
+        if (mBluetoothGattServer != null) {
+            mBluetoothGattServer!!.close()
+        }
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            stopForeground(true)// 停止前台服务--参数:表示是否移除之前的通知
+        }
+    }
+
+    inner class ServiceBinder : Binder() {
+        fun doThings() {}
+    }
+
+
+
+    ///////////////********************蓝牙ble配网相关****************************///////////////
+    private fun configBle() {
+        bleHandler.removeCallbacksAndMessages(null)
+        val bluetoothManager = getSystemService(BLUETOOTH_SERVICE) as BluetoothManager
+        val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
+        //打开蓝牙
+        if (bluetoothAdapter != null) {
+            if (!bluetoothAdapter.isEnabled) {
+                Log.e(TAG, "蓝牙未开启")
+                //直接开启蓝牙
+                bluetoothAdapter.enable()
+
+                //15s后再检查
+                bleHandler.postDelayed({
+                    configBle()
+                }, 15000)
+            } else {
+                // ============启动BLE蓝牙广播 =================
+                Log.d(TAG, "蓝牙已开启")
+                //广播设置(必须)
+                val settings = AdvertiseSettings.Builder()
+                    .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) //广播模式: 低功耗,平衡,低延迟
+                    .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) //发射功率级别: 极低,低,中,高
+                    .setConnectable(true) //能否连接,广播分为可连接广播和不可连接广播
+                    .build()
+
+                //广播数据(必须,广播启动就会发送)
+                val advertiseData = AdvertiseData.Builder()
+                    .setIncludeDeviceName(true) //包含蓝牙名称
+                    .setIncludeTxPowerLevel(true) //包含发射功率级别
+                    .addManufacturerData(1, byteArrayOf(23, 33)) //设备厂商数据,自定义
+                    .build()
+
+                //扫描响应数据(可选,当客户端扫描时才发送)
+                val scanResponse = AdvertiseData.Builder()
+                    .addManufacturerData(2, byteArrayOf(66, 66)) //设备厂商数据,自定义
+                    .addServiceUuid(ParcelUuid(BLE_Constants.UUID_SERVICE)) //服务UUID
+                    //.addServiceData(ParcelUuid(BLE_Constants.UUID_SERVICE), byteArrayOf(2)) //服务数据,自定义
+                    .build()
+
+                mBluetoothLeAdvertiser = bluetoothAdapter.bluetoothLeAdvertiser
+                mBluetoothLeAdvertiser?.startAdvertising(
+                    settings,
+                    advertiseData,
+                    scanResponse,
+                    mAdvertiseCallback
+                )
+
+                // 注意:必须要开启可连接的BLE广播,其它设备才能发现并连接BLE服务端!
+                // =============启动BLE蓝牙服务端=====================================================================================
+                val service = BluetoothGattService(BLE_Constants.UUID_SERVICE, BluetoothGattService.SERVICE_TYPE_PRIMARY)
+
+                //添加可读+通知characteristic
+                val characteristicRead = BluetoothGattCharacteristic(
+                    BLE_Constants.UUID_CHAR_READ_NOTIFY,
+                    BluetoothGattCharacteristic.PROPERTY_READ or BluetoothGattCharacteristic.PROPERTY_NOTIFY,
+                    BluetoothGattCharacteristic.PERMISSION_READ
+                )
+
+                characteristicRead.addDescriptor(
+                    BluetoothGattDescriptor(
+                        BLE_Constants.UUID_DESC_NOTITY,
+                        BluetoothGattCharacteristic.PERMISSION_WRITE
+                    )
+                )
+                service.addCharacteristic(characteristicRead)
+
+                //添加可写characteristic
+                val characteristicWrite = BluetoothGattCharacteristic(
+                    BLE_Constants.UUID_CHAR_WRITE,
+                    BluetoothGattCharacteristic.PROPERTY_WRITE,
+                    BluetoothGattCharacteristic.PERMISSION_WRITE
+                )
+                service.addCharacteristic(characteristicWrite)
+
+                if (bluetoothManager != null) {
+                    mBluetoothGattServer = bluetoothManager.openGattServer(this, mBluetoothGattServerCallback)
+                }
+                mBluetoothGattServer?.addService(service)
+            }
+        }
+    }
+
+    // BLE广播Callback
+    private val mAdvertiseCallback = object: AdvertiseCallback() {
+        override fun onStartSuccess(settingsInEffect: AdvertiseSettings) {
+            Log.d(TAG, "BLE广播开启成功")
+        }
+
+        override fun onStartFailure(errorCode: Int) {
+            Log.e(TAG, "BLE广播开启失败: $errorCode")
+        }
+    }
+
+    // BLE服务端Callback
+    private val mBluetoothGattServerCallback = object: BluetoothGattServerCallback() {
+
+        override fun onConnectionStateChange(device: BluetoothDevice?, status: Int, newState: Int) {
+            Log.i(TAG,
+                String.format(
+                    "onConnectionStateChange:%s,%s,%s,%s",
+                    device!!.name,
+                    device.address,
+                    status,
+                    newState
+                )
+            )
+        }
+
+        override fun onServiceAdded(status: Int, service: BluetoothGattService) {
+            Log.i(TAG, String.format("onServiceAdded:%s,%s", status, service.uuid))
+        }
+
+        override fun onCharacteristicReadRequest(
+            device: BluetoothDevice,
+            requestId: Int,
+            offset: Int,
+            characteristic: BluetoothGattCharacteristic
+        ) {
+            Log.i(TAG, String.format(
+                "onCharacteristicReadRequest:%s,%s,%s,%s,%s",
+                device.name,
+                device.address,
+                requestId,
+                offset,
+                characteristic.uuid
+            )
+            )
+            val response = "CHAR_" + (Math.random() * 100).toInt() //模拟数据
+
+            mBluetoothGattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, response.toByteArray()) // 响应客户端
+
+            Log.i(TAG, "客户端读取Characteristic[" + characteristic.getUuid() + "]:\n" + response)
+        }
+
+        override fun onCharacteristicWriteRequest(
+            device: BluetoothDevice,
+            requestId: Int,
+            characteristic: BluetoothGattCharacteristic,
+            preparedWrite: Boolean,
+            responseNeeded: Boolean,
+            offset: Int,
+            value: ByteArray
+        ) {
+            // 获取客户端发过来的数据
+            val requestStr = String(value)
+            Log.i(TAG, String.format(
+                "onCharacteristicWriteRequest:%s,%s,%s,%s,%s,%s,%s,%s",
+                device.name,
+                device.address,
+                requestId,
+                characteristic.uuid,
+                preparedWrite,
+                responseNeeded,
+                offset,
+                requestStr
+            )
+            )
+            mBluetoothGattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value) // 响应客户端
+            Log.i(TAG, "客户端写入Characteristic[" + characteristic.getUuid() + "]:\n" + requestStr)
+
+            //todo: 这里收到配网数据,待处理
+        }
+
+        override fun onDescriptorReadRequest(
+            device: BluetoothDevice,
+            requestId: Int,
+            offset: Int,
+            descriptor: BluetoothGattDescriptor
+        ) {
+            Log.i(TAG,
+                String.format(
+                    "onDescriptorReadRequest:%s,%s,%s,%s,%s",
+                    device.name,
+                    device.address,
+                    requestId,
+                    offset,
+                    descriptor.uuid
+                )
+            )
+            val response = "DESC_" + (Math.random() * 100).toInt() //模拟数据
+            mBluetoothGattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, response.toByteArray()) // 响应客户端
+            Log.i(TAG, "客户端读取Descriptor[" + descriptor.getUuid() + "]:\n" + response)
+        }
+
+        override fun onDescriptorWriteRequest(
+            device: BluetoothDevice,
+            requestId: Int,
+            descriptor: BluetoothGattDescriptor,
+            preparedWrite: Boolean,
+            responseNeeded: Boolean,
+            offset: Int,
+            value: ByteArray
+        ) {
+            // 获取客户端发过来的数据
+            val valueStr = Arrays.toString(value)
+            Log.i(TAG, String.format(
+                "onDescriptorWriteRequest:%s,%s,%s,%s,%s,%s,%s,%s",
+                device.name,
+                device.address,
+                requestId,
+                descriptor.uuid,
+                preparedWrite,
+                responseNeeded,
+                offset,
+                valueStr
+            )
+            )
+            mBluetoothGattServer?.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value) // 响应客户端
+            Log.i(TAG, "客户端写入Descriptor[" + descriptor.getUuid() + "]:\n" + valueStr)
+        }
+
+        override fun onExecuteWrite(device: BluetoothDevice, requestId: Int, execute: Boolean) {
+            Log.i(TAG, String.format("onExecuteWrite:%s,%s,%s,%s", device.getName(), device.getAddress(), requestId, execute))
+        }
+
+        override fun onNotificationSent(device: BluetoothDevice, status: Int) {
+            Log.i(TAG, String.format("onNotificationSent:%s,%s,%s", device.getName(), device.getAddress(), status))
+        }
+
+        override fun onMtuChanged(device: BluetoothDevice, mtu: Int) {
+            Log.i(TAG, String.format("onMtuChanged:%s,%s,%s", device.getName(), device.getAddress(), mtu));
+        }
+    }
+}

app/src/main/res/drawable/ic_launch_48.xml → android_bed/src/main/res/drawable/ic_launch_48.xml


+ 25 - 0
android_bed/src/main/res/layout/activity_bleserver.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:padding="2dp">
+
+    <ScrollView
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:background="@drawable/shape_nurse_bg"
+        android:padding="2dp"
+        android:scrollbars="none"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <TextView
+            android:id="@+id/tv_tips"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+    </ScrollView>
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 4 - 0
android_bed/src/main/sharedUserId/AndroidManifest.xml

@@ -60,6 +60,10 @@
     <!-- 如果设配Android9及更低版本,可以申请 ACCESS_COARSE_LOCATION -->
     <!-- 如果设配Android9及更低版本,可以申请 ACCESS_COARSE_LOCATION -->
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
 
 
+    <!-- required = true 表示手机必须支持BLE,否则无法安装运行-->
+    <uses-feature
+        android:name="android.hardware.bluetooth_le"
+        android:required="false" />
 
 
     <uses-feature
     <uses-feature
         android:name="android.hardware.nfc"
         android:name="android.hardware.nfc"