网站建设专员工作,慧聪网郭凡生现状,宠物医院网站建设,dw网站建设怎么放在网上Ble蓝牙App#xff08;二#xff09;连接与发现服务 前言正文一、GATT回调二、连接和断连三、连接状态回调四、发现服务五、服务适配器六、显示服务七、源码 前言 在上一篇中我们进行扫描设备的处理#xff0c;本文中进行连接和发现服务的数据处理#xff0c;运行效果图如下… Ble蓝牙App二连接与发现服务 前言正文一、GATT回调二、连接和断连三、连接状态回调四、发现服务五、服务适配器六、显示服务七、源码 前言 在上一篇中我们进行扫描设备的处理本文中进行连接和发现服务的数据处理运行效果图如下所示
正文 现在我们从MainActivity进入到ScanActivity选中一个设备返回到MainActivity下面要对选中的设备进行处理首先我们来做连接。
一、GATT回调 在之前我们写了一个BleCore这里面是对扫描的封装那么对于连接来说我们同样可以封装到这里我们可以在BleCore中写一个BleGattCallback 类代码如下所示
class BleGattCallback : BluetoothGattCallback() {/*** 连接状态改变*/override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {}/*** 发现服务*/override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {}}因为本文要做的事情是连接和发现服务所以我们就先重写这两个函数注意一点的是蓝牙的操作都是在子线程中进行的如果我们需要知道当前是否连接则需要写一个接口用于回调到Activity中在ble包下新建一个BleCallback接口代码如下所示
interface BleCallback {/*** 设备的所有信息*/fun deviceInfo(info: String)/*** 连接状态* param state true or false*/fun onConnectionStateChange(state: Boolean)/*** 发现服务*/fun onServicesDiscovered(services: ListBluetoothGattService)
}接口中定义了三个函数通过注释我们清晰的知道都是什么作用这里着重介绍第一个函数这个函数会显示设备各个时候的状态信息从连接之后的所有动作如果我们需要保存设备的操作日志的话可以通过这个来进行处理保存。
然后回到BleCore在companion object中声明变量和设置接口回调的函数 SuppressLint(StaticFieldLeak)companion object {...private var mGatt: BluetoothGatt? nullprivate var mBleCallback: BleCallback? nullprivate lateinit var mBleGattCallback: BleGattCallback/*** 是否连接*/private var mIsConnected false/*** 设备信息*/private fun deviceInfo(info: String) mBleCallback?.deviceInfo(info)/*** 连接状态*/private fun connectState(state: Boolean) {mIsConnected statemBleCallback?.onConnectionStateChange(state)}}同时在 companion object外创建一个函数代码如下所示 fun setBleCallback(bleCallback: BleCallback) {mBleCallback bleCallback}此函数和setPhyScanCallback()函数是同级的下面我们增加连接和断连的函数。
二、连接和断连
在BleCore中增加如下代码 /*** 连接蓝牙设备*/fun connect(device: BluetoothDevice) {deviceInfo(连接中...)mGatt if (Build.VERSION.SDK_INT Build.VERSION_CODES.O) {device.connectGatt(context, false, mBleGattCallback, BluetoothDevice.TRANSPORT_LE, BluetoothDevice.PHY_LE_2M_MASK)} else {device.connectGatt(context, false, mBleGattCallback)}}/*** 断开连接*/fun disconnect() {deviceInfo(断开连接...)mGatt?.disconnect()}连接与断开连接调用时会触发onConnectionStateChange()函数。
三、连接状态回调
下面修改这个函数的代码如下所示 override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {val address gatt.device.addresswhen (newState) {BluetoothProfile.STATE_CONNECTED - {deviceInfo(已连接$address)connectState(true)}BluetoothProfile.STATE_DISCONNECTED - {deviceInfo(已断开连接$address)connectState(false)}else - {Log.d(TAG, onConnectionStateChange: $status)connectState(false)mGatt?.close()mGatt null}}}在回调中连接成功和断开连接都会有一个对应的状态码通过状态回调到接口函数中然后回到MainActivity中使用一下这个回调首先我们修改一下activity_main.xml中的代码如下所示
?xml version1.0 encodingutf-8?
androidx.constraintlayout.widget.ConstraintLayout xmlns:androidhttp://schemas.android.com/apk/res/androidxmlns:apphttp://schemas.android.com/apk/res-autoxmlns:toolshttp://schemas.android.com/toolsandroid:layout_widthmatch_parentandroid:layout_heightmatch_parenttools:context.MainActivitycom.google.android.material.appbar.MaterialToolbarandroid:idid/toolbarandroid:layout_widthmatch_parentandroid:layout_height?attr/actionBarSizeandroid:backgroundcolor/orangeapp:layout_constraintEnd_toEndOfparentapp:layout_constraintStart_toStartOfparentapp:layout_constraintTop_toTopOfparentapp:navigationIcondrawable/ic_scan_bleapp:titleGoodBleapp:titleCenteredtrueapp:titleTextColorcolor/whiteTextViewandroid:idid/tv_disconnectandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:layout_gravityendandroid:layout_marginEnd8dpandroid:visibilitygoneandroid:padding8dpandroid:text断开连接android:textColorcolor/white //com.google.android.material.appbar.MaterialToolbarTextViewandroid:idid/tv_device_infoandroid:layout_width0dpandroid:layout_heightwrap_contentandroid:padding16dpandroid:text设备信息app:layout_constraintEnd_toEndOfparentapp:layout_constraintStart_toStartOfparentapp:layout_constraintTop_toBottomOfid/toolbar //androidx.constraintlayout.widget.ConstraintLayout在XML中只增加了两个TextView分别用于断连和显示设备状态然后我们修改MainActivity中的代码如下所示
class MainActivity : BaseActivity(), BleCallback {private val binding by viewBinding(ActivityMainBinding::inflate)private lateinit var bleCore: BleCoreSuppressLint(MissingPermission)private val scanIntent registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -if (result.resultCode Activity.RESULT_OK) {if (result.data null) returnregisterForActivityResult//获取选中的设备val device if (Build.VERSION.SDK_INT Build.VERSION_CODES.TIRAMISU) {result.data!!.getParcelableExtra(device, BluetoothDevice::class.java)} else {result.data!!.getParcelableExtra(device) as BluetoothDevice?}//连接设备if (device ! null) bleCore.connect(device)}}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)bleCore (application as BleApp).getBleCore()bleCore.setBleCallback(thisMainActivity)//进入扫描页面binding.toolbar.setNavigationOnClickListener { scanIntent.launch(Intent(this,ScanActivity::class.java)) }//断开连接binding.tvDisconnect.setOnClickListener {binding.tvDisconnect.visibility View.GONEbleCore.disconnect()}}override fun deviceInfo(info: String) {runOnUiThread {binding.tvDeviceInfo.text info}}override fun onConnectionStateChange(state: Boolean) {runOnUiThread {if (state) binding.tvDisconnect.visibility View.VISIBLE}}override fun onServicesDiscovered(services: ListBluetoothGattService) {}
}这里我们首先是通过Activity Result API的StartActivityForResult()函数进行页面跳转在返回的时候拿到device对象这在前一篇已经写好了拿到device对象之后调用BleCore的connect()函数进行连接设备在onCreate()函数中进行BleCore的赋值然后设置Ble的回调实现BleCallback接口重写里面的函数当连接成功之后会通过回调deviceInfo()得到设备状态因为是子线程所以在ui线程中渲染UI。而onConnectionStateChange()函数回调连接成功或者失败如果成功则为ture就显示tvDisconnect控件此时连接成功点击这个tvDisconnect就会断开连接点击监听就在onCreate()中写好了下面我们运行一下看看效果。 从这个效果图来看我们连接成功之后有状态点击断开连接也会有状态改变那么连接就写好了。
四、发现服务 连接写好了下面可以写发现服务了我们可以在连接成功的处理中进行发现服务下面我们修改一下BleGattCallback中的onConnectionStateChange()函数中的代码如下图所示 通过gatt.discoverServices()进行发现服务的动作在此之前通过deviceInfo设置当前的动作状态发现服务执行会触发onServicesDiscovered()回调在这个回调中我们可以回调到页面修改代码如下所示 override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {if (status BluetoothGatt.GATT_SUCCESS) {deviceInfo(发现了 ${gatt.services.size} 个服务)gatt.services?.let { mBleCallback?.onServicesDiscovered(it) }}}在回调中设置发现服务的个数然后回调因为服务是多个的那么下面我们就需要使用一个列表是装载服务首先我们修改一下activity_main.xml在里面增加一个RecyclerView代码如下所示
?xml version1.0 encodingutf-8?
androidx.constraintlayout.widget.ConstraintLayout......androidx.recyclerview.widget.RecyclerViewandroid:idid/rv_serviceandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:backgroundcolor/whiteapp:layout_constraintEnd_toEndOfparentapp:layout_constraintStart_toStartOfparentapp:layout_constraintTop_toBottomOfid/tv_device_info //androidx.constraintlayout.widget.ConstraintLayout五、服务适配器 要显示服务列表数据首先需要一个适配器而适配器又需要一个item去渲染数据下面我们在layout下创建一个item_service.xml代码如下所示
?xml version1.0 encodingutf-8?
androidx.constraintlayout.widget.ConstraintLayout xmlns:androidhttp://schemas.android.com/apk/res/androidxmlns:apphttp://schemas.android.com/apk/res-autoandroid:idid/item_serviceandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:layout_marginBottom2dpandroid:backgroundcolor/whiteandroid:orientationverticalTextViewandroid:idid/tv_service_nameandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:layout_marginStart16dpandroid:layout_marginTop8dpandroid:text服务android:textColorcolor/blackandroid:textSize16spandroid:textStyleboldapp:layout_constraintStart_toStartOfparentapp:layout_constraintTop_toTopOfparent /TextViewandroid:idid/tv_uuid_titleandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:textUUIDapp:layout_constraintStart_toStartOfid/tv_service_nameapp:layout_constraintTop_toBottomOfid/tv_service_name /TextViewandroid:idid/tv_service_uuidandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:textUUIDandroid:textColorcolor/blackapp:layout_constraintBottom_toBottomOfid/tv_uuid_titleapp:layout_constraintStart_toEndOfid/tv_uuid_titleapp:layout_constraintTop_toTopOfid/tv_uuid_title /TextViewandroid:idid/tv_service_infoandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:layout_marginBottom8dpandroid:textPRIMARY SERVICEapp:layout_constraintBottom_toBottomOfparentapp:layout_constraintStart_toStartOfid/tv_service_nameapp:layout_constraintTop_toBottomOfid/tv_uuid_title //androidx.constraintlayout.widget.ConstraintLayout下面我们在ble包下新建一个BleUtils类代码如下所示
object BleUtils {private val generic -0000-1000-8000-00805F9B34FB/*** 获取蓝牙服务名称* param uuid UUID*/fun getServiceName(uuid: UUID) when (0x${uuid.toString().substring(4, 8).uppercase(Locale.getDefault())}) {0x1800 - Generic Access service0x1801 - Generic Attribute service0x1802 - Immediate Alert service0x1803 - Link Loss service0x1804 - Tx Power service0x1805 - Current Time service0x1806 - Reference Time Update service0x1807 - Next DST Change service0x1808 - Glucose service0x1809 - Health Thermometer service0x180A - Device Information service0x180D - Heart Rate service0x180E - Phone Alert Status service0x180F - Battery service0x1810 - Blood Pressure service0x1811 - Alert Notification service0x1812 - Human Interface Device service0x1813 - Scan Parameters service0x1814 - Running Speed and Cadence service0x1815 - Automation IO service0x1816 - Cycling Speed and Cadence service0x1818 - Cycling Power service0x1819 - Location and Navigation service0x181A - Environmental Sensing service0x181B - Body Composition service0x181C - User Data service0x181D - Weight Scale service0x181E - Bond Management service0x181F - Continuous Glucose Monitoring service0x1820 - Internet Protocol Support service0x1821 - Indoor Positioning service0x1822 - Pulse Oximeter service0x1823 - HTTP Proxy service0x1824 - Transport Discovery service0x1825 - Object Transfer service0x1826 - Fitness Machine service0x1827 - Mesh Provisioning service0x1828 - Mesh Proxy service0x1829 - Reconnection Configuration service0x183A - Insulin Delivery service0x183B - Binary Sensor service0x183C - Emergency Configuration service0x183D - Authorization Control service0x183E - Physical Activity Monitor service0x183F - Elapsed Time service0x1840 - Generic Health Sensor service0x1843 - Audio Input Control service0x1844 - Volume Control service0x1845 - Volume Offset Control service0x1846 - Coordinated Set Identification service0x1847 - Device Time service0x1848 - Media Control service0x1849 - Generic Media Control service0x184A - Constant Tone Extension service0x184B - Telephone Bearer service0x184C - Generic Telephone Bearer service0x184D - Microphone Control service0x184E - Audio Stream Control service0x184F - Broadcast Audio Scan service0x1850 - Published Audio Capabilities service0x1851 - Basic Audio Announcement service0x1852 - Broadcast Audio Announcement service0x1853 - Common Audio service0x1854 - Hearing Access service0x1855 - Telephony and Media Audio service0x1856 - Public Broadcast Announcement service0x1857 - Electronic Shelf Label serviceelse - Unknown Service}fun getServiceUUID(uuid: UUID) 0x${uuid.toString().substring(4, 8).uppercase(Locale.getDefault())}
}这里需要说明一下蓝牙的UUID蓝牙UUIDUniversally Unique Identifier是用于唯一标识蓝牙设备和服务的一种标识符。它是一个128位长的数字在蓝牙通信中起到唯一标识的作用。蓝牙UUID按照标准分为两种类型 16位UUID这些UUID通常用于蓝牙标准定义的一些通用服务和特性。例如设备名称服务的UUID是 00001800-0000-1000-8000-00805F9B34FB。 128位UUID这些UUID通常用于自定义的服务和特性以确保全球唯一性。可以自行生成一个128位的UUID作为自定义的服务或特性标识。例如一个自定义的服务UUID可以是 0000XXXX-0000-1000-8000-00805F9B34FB其中的 XXXX 部分可以是任意的16进制数字。
在蓝牙通信中设备使用UUID来发布和查找服务以及识别特性。UUID是蓝牙设备之间进行通信时的重要标识确保了设备和服务的唯一性。
那么getServiceName()中的键你就知道是什么意思了0x1800就是16进制数字而对应的值则是SIG定义的可以参考这个文档Assigned_Numbers.pdf。如果你的值找不到对应的那说明它不是SIG规范的你这个服务UUID就是自己公司自定义的。
下面我们写适配器在adapter包下新建一个ServiceAdapter类代码如下所示
class ServiceAdapter(private val services: ListBluetoothGattService
) : RecyclerView.AdapterServiceAdapter.ViewHolder() {private var mOnItemClickListener: OnItemClickListener? nullfun setOnItemClickListener(mOnItemClickListener: OnItemClickListener?) {this.mOnItemClickListener mOnItemClickListener}override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {val viewHolder ViewHolder(ItemServiceBinding.inflate(LayoutInflater.from(parent.context), parent, false))viewHolder.binding.itemService.setOnClickListener { mOnItemClickListener?.onItemClick(it, viewHolder.adapterPosition) }return viewHolder}override fun onBindViewHolder(holder: ViewHolder, position: Int) {holder.binding.tvServiceName.text BleUtils.getServiceName(services[position].uuid)holder.binding.tvServiceUuid.text BleUtils.getServiceUUID(services[position].uuid)}override fun getItemCount() services.sizeclass ViewHolder(itemView: ItemServiceBinding) : RecyclerView.ViewHolder(itemView.root) {var binding: ItemServiceBindinginit {binding itemView}}
}这里的代码就是比较简单的就是基本的写法下面回到MainActivity中进行显示数据。
六、显示服务
首先声明变量 private var mServiceAdapter: ServiceAdapter? nullprivate val mServiceList: MutableListBluetoothGattService mutableListOf()然后实现OnItemClickListener 接口
class MainActivity : BaseActivity(), BleCallback, OnItemClickListener {重写onItemClick()函数。 override fun onItemClick(view: View?, position: Int) {showMsg(mServiceList[position].uuid.toString())}修改onServicesDiscovered()函数代码如下所示 override fun onServicesDiscovered(services: ListBluetoothGattService) {runOnUiThread {mServiceList.clear()mServiceList.addAll(services)mServiceAdapter ?: run {mServiceAdapter ServiceAdapter(mServiceList)binding.rvService.apply {(itemAnimator as SimpleItemAnimator).supportsChangeAnimations falselayoutManager LinearLayoutManager(thisMainActivity)adapter mServiceAdapter}mServiceAdapter!!.setOnItemClickListener(thisMainActivity)mServiceAdapter}mServiceAdapter!!.notifyDataSetChanged()}}这里的写法其实和扫描设备哪里如出一辙下面我们运行一下看看什么效果。
七、源码
如果对你有所帮助的话不妨 Star 或 Fork山高水长后会有期~
源码地址GoodBle