安卓BLE蓝牙通讯

蓝牙测试demo
简介
  Android手机间通过蓝牙方式进行通信,有两种常见的方式,一种是socket方式(传统蓝牙),另一种是通过GATT(BLE蓝牙)。与传统蓝牙相比,BLE 旨在大幅降低功耗。这样一来,应用就可以与功率要求更严格的 BLE 设备(如近程传感器、心率监测器和健身设备)进行通信。

实现
1.权限
  如需使用BLE蓝牙 API,需要在AndroidManifest.xml清单文件中声明多项权限,并动态获取相应权限。一旦应用获得使用蓝牙的权限,应用就需要访问 BluetoothAdapter 并确定设备是否支持蓝牙。如果蓝牙可用,设备将扫描附近的 BLE 设备。发现设备后,系统会连接到 BLE 设备上的 GATT 服务器来发现 BLE 设备的功能。建立连接后,可以根据可用的服务和特征通过已连接的设备传输数据。
  在高版本的安卓设备中,除蓝牙基本权限外,还需用到位置权限,否则会扫描不到蓝牙。
  AndroidManifest.xml文件如下:

    <uses-permission android:name="android.permission.BLUETOOTH" /><uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /><uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /><uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /><uses-permission android:name="android.permission.BLUETOOTH_SCAN" /><uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

动态权限申请如下,可放在MainActivity中。

    // todo 动态申请权限private void initPermission() {List<String> mPermissionList = new ArrayList<>();if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {// Android 版本大于等于 Android12 时mPermissionList.add(android.Manifest.permission.BLUETOOTH_SCAN);mPermissionList.add(android.Manifest.permission.BLUETOOTH_ADVERTISE);mPermissionList.add(android.Manifest.permission.BLUETOOTH_CONNECT);}mPermissionList.add(android.Manifest.permission.ACCESS_COARSE_LOCATION);mPermissionList.add(android.Manifest.permission.ACCESS_FINE_LOCATION);if (mPermissionList.size() > 0) {ActivityCompat.requestPermissions(this, mPermissionList.toArray(new String[0]), 1001);}}

2.扫描蓝牙设备

    //默认扫描BLE蓝牙时长10s 可更改private final Long mScanTime = 10 * 1000L;private final List<ScanFilter> mScanFilterList = new ArrayList<>();private final ScanSettings mScanSettings = new ScanSettings.Builder().build();/*** 蓝牙扫描*/@SuppressLint("MissingPermission")public void startScanBle() {if (!getBleEnable()) {
//            openBlueTooth();TimerUtil.cancelTimer();ToastUtil.showToast("蓝牙未打开", 2000);return;}if (!GpsAdmin.getInstance().isGpsOn()) {TimerUtil.cancelTimer();ToastUtil.showToast("定位未打开", 2000);return;}Log.i(TAG, "开始扫描蓝牙");mBleCurrentInfo.clearBleResult();BleCallback.instance.startLeScan(mBluetoothAdapter, new ScanCallback() {@Overridepublic void onScanResult(int callbackType, ScanResult result) {super.onScanResult(callbackType, result);try {if (result.getDevice() == null || result.getDevice().getAddress() == null) {return;}if (result.getDevice().getName() == null || Objects.equals(result.getDevice().getName(), "")) {return;}for (BleDeviceBean bean : mBleCurrentInfo.getBleList()) {if (Objects.equals(bean.getBleAddress(), result.getDevice().getAddress())) {return;}}Log.i(TAG, "NAME:" + result.getDevice().getName() + ",ADDRESS:" + result.getDevice().getAddress());BleDeviceBean bean = new BleDeviceBean(result.getDevice().getName(), result.getDevice().getAddress(), false);mBleCurrentInfo.addBleResult(bean);} catch (Exception e) {Log.i(TAG, e.toString());}}@Overridepublic void onBatchScanResults(List<ScanResult> results) {super.onBatchScanResults(results);}@Overridepublic void onScanFailed(int errorCode) {super.onScanFailed(errorCode);}}, mScanTime, mScanFilterList, mScanSettings);}

  BleCallback中startLeScan()方法如下。

    /*** 开启ble扫描*/@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)public void startLeScan(BluetoothAdapter bluetoothAdapter,ScanCallback scanCallback,Long scanTime,List<ScanFilter> filters,ScanSettings scanSettings) {try {if (bluetoothAdapter == null) {return;}if (scanCallback == null) {return;}BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();if (bluetoothLeScanner == null) {return;}if (mScanCallback != null && bluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {bluetoothLeScanner.stopScan(mScanCallback);mScanCallback = null;}TimerUtil.cancelDialogTimer();mScanCallback = scanCallback;bluetoothLeScanner.startScan(filters,scanSettings,mScanCallback);TimerUtil.startDialogTask(new TimerTask() {@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)@Overridepublic void run() {if (mScanCallback != null && bluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {bluetoothLeScanner.stopScan(mScanCallback);mScanCallback = null;}}}, scanTime);} catch (Exception e) {Log.i(TAG, "蓝牙扫描异常");}}

3.连接GATT服务器

    public void connect(Boolean isAutoConnect,BluetoothDevice bluetoothDevice) {bluetoothDevice.connectGatt(Utils.getApp(), isAutoConnect, this, BluetoothDevice.TRANSPORT_LE);}

  在设备连接蓝牙后会触发onConnectionStateChange回调。

public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {Log.i(TAG, "onConnectionStateChange,status:" + status +",newState:" + newState);switch (newState) {case BluetoothProfile.STATE_CONNECTED:BleDevice bleDevice = new BleDevice();bleDevice.setDeviceName(gatt.getDevice().getName());bleDevice.setMacAddress(gatt.getDevice().getAddress());bleDevice.setGatt(gatt);mConnectedBleDeviceList.add(bleDevice);Log.i(TAG, gatt.getDevice().getAddress() + "已连接");if (mConnectListener != null) {mConnectListener.onConnectSuccess(gatt);}try {Thread.sleep(50);} catch (InterruptedException e) {Log.i(TAG, e.toString());}gatt.discoverServices();break;case BluetoothProfile.STATE_DISCONNECTED:Iterator<BleDevice> iterator = mConnectedBleDeviceList.iterator();while (iterator.hasNext()) {BleDevice next = iterator.next();if (Objects.equals(next.getMacAddress(), gatt.getDevice().getAddress())) {iterator.remove();}}Log.i(TAG, gatt.getDevice().getAddress() + "断开连接");if (mConnectListener != null) {mConnectListener.onDisconnect(gatt);}gatt.close();break;default:break;}}

  当newState值为BluetoothProfile.STATE_CONNECTED表示连接外围设备成功,其中gatt.discoverServices()方法尤为关键,如果不调用此方法,就无法触发onServicesDiscovered()回调,就无法发现所连接蓝牙设备的服务UUID;除此之外,如果连接上后立刻调用此方法,会有可能无法触发onServicesDiscovered()方法。

4.绑定UUID
  在设备触发onServicesDiscovered回调时,获取到蓝牙所有服务。

    public void onServicesDiscovered(BluetoothGatt gatt, int status) {Log.i(TAG, "onServicesDiscovered,status:" + status);switch (status) {case BluetoothGatt.GATT_SUCCESS:if (!mConnectedBleDeviceList.isEmpty()) {mConnectedBleDeviceList.get(mConnectedBleDeviceList.size() - 1).setServiceList(gatt.getServices());if (mConnectListener != null) {mConnectListener.onServicesDiscovered(gatt, gatt.getServices());}}break;default:if (mConnectListener != null) {mConnectListener.onServicesDiscoverFailed(status);}break;}}

  正常情况下,当触发触发onConnectSuccess回调时,设备已经连上的蓝牙的GATT,此时可认为蓝牙已连接,此时中心设备是无法和外围设备通讯的,还需根据蓝牙协议绑定指定的特征UUID,一般中心设备接收数据的为NOTIFY_CHARACTERISTIC_UUID,向外围设备发送消息的为WRITE_CHARACTERISTIC_UUID,大部分外设备还有描述符,例如接收数据需要的描述符NOTIFY_DESCRIPTOR_UUID等。下面是我的蓝牙设备使用到的UUID。

public class UsedUUID {public static UUID SERVICE_UUID = UUID.fromString("0783b03e-8535-b5a0-7140-a304d2495cb7");public static UUID WRITE_CHARACTERISTIC_UUID = UUID.fromString("0783b03e-8535-b5a0-7140-a304d2495cba");public static UUID NOTIFY_CHARACTERISTIC_UUID = UUID.fromString("0783b03e-8535-b5a0-7140-a304d2495cb8");public static UUID NOTIFY_DESCRIPTOR_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
}

  获取到所有服务后,根据蓝牙协议,绑定指定的特征UUID。此demo中实际以设备找到蓝牙服务指定UUID,并绑定成功视为真正连接成功。

public void onServicesDiscovered(BluetoothGatt gatt, List<BluetoothGattService> services) {Log.i(TAG, "发现服务UUID");boolean isNext = false;for (BluetoothGattService service: services) {if (service.getUuid().toString().equals(UsedUUID.SERVICE_UUID.toString())) {Log.i(TAG, "找到SERVICE_UUID");mOperateService = service;mBleGattCharacteristics.clear();mBleGattCharacteristics.addAll(service.getCharacteristics());isNext = true;break;}}if (!isNext) {Log.i(TAG, "未找到指定的服务UUID");disConnectGatt(false);return;}isNext = false;List<BluetoothGattCharacteristic> bleGattCharacteristics = new ArrayList<>(mBleGattCharacteristics);for (BluetoothGattCharacteristic bluetoothGattCharacteristic: bleGattCharacteristics) {if (bluetoothGattCharacteristic.getUuid().toString().equals(UsedUUID.NOTIFY_CHARACTERISTIC_UUID.toString())) {Log.i(TAG, "找到NOTIFY_CHARACTERISTIC_UUID");mNotifyCharacteristic = bluetoothGattCharacteristic;isNext = true;}if (bluetoothGattCharacteristic.getUuid().toString().equals(UsedUUID.WRITE_CHARACTERISTIC_UUID.toString())) {Log.i(TAG, "找到WRITE_CHARACTERISTIC_UUID");mWriteCharacteristic = bluetoothGattCharacteristic;}}if (isNext) {try {Log.i(TAG, "BLE蓝牙连接成功");if (mBleCurrentInfo.isConnect()) {mBleCurrentInfo.setConnect(false);mOperateGatt.close();}for (BleDeviceBean bean: mBleCurrentInfo.getBleList()) {bean.setBleConnect(false);}mOperateGatt = gatt;subscribeNotify();Log.i(TAG, "BLE蓝牙绑定成功");mBleCurrentInfo.setConnect(true);if (mIsFirstConnect) {mBleCurrentInfo.getBleList().get(0).setBleConnect(true);}
//                if (Objects.equals(mBleCurrentInfo.getBleAddress(), gatt.getDevice().getAddress()) && !mBleCurrentInfo.getBleList().isEmpty()) {
//                    mBleCurrentInfo.getBleList().get(0).setBleConnect(true);
//                }mBleCurrentInfo.setConnect(true);mBleCurrentInfo.setBleName(gatt.getDevice().getName());mBleCurrentInfo.setBleAddress(gatt.getDevice().getAddress());mBleCurrentInfo.updateScanBean();if (mDiscoveredListener != null) {mDiscoveredListener.onServicesDiscovered(gatt);}if (mBleListener != null) {mBleListener.onConnectSuccess(true);}} catch (Exception e) {Log.i(TAG, "BLE蓝牙连接异常");disConnectGatt(false);}} else {disConnectGatt(false);}}

5.接收消息
  所有接收的消息都是触发连接蓝牙时传入的BluetoothGattCallback回调中onCharacteristicChanged方法,外围设备会将数据的变更写入属性为NOTIFY的BluetoothGattCharacteristic中,每当BluetoothGattCharacteristic的值发生变化时,中心设备都会收到通知。

    public void onCharacteristicChanged(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic) {if (mNotifyListener != null) {mNotifyListener.onCharacteristicChange(gatt, characteristic);}}

6.发送消息
  发送消息时,我们中心设备只需改变属性为WRITED的BluetoothGattCharacteristic值,外围设备就可以收到我们发送的消息。

    public void sendMessage(byte[] message) {if (!mBleCurrentInfo.isConnect()) {ToastUtil.showToast("蓝牙未连接,请连接蓝牙!", 2000);return;}if (mOperateGatt == null || mWriteCharacteristic == null) {Log.i(TAG, "发送消息失败,未持有相关服务");return;}mWriteCharacteristic.setValue(message);mOperateGatt.writeCharacteristic(mWriteCharacteristic);}

7.效果演示
在这里插入图片描述

这里以发送HD+RPC=3,{“paramtype”:1}为例子,将String转为byte数组后发送给外围设备,接收外围设备数据为byte数组,未经过转换。
8.核心代码
① BleAdmin完整代码:

package com.example.bluetooth.util;import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.content.Context;
import android.util.Log;import com.example.bluetooth.bean.BleCurrentInfo;
import com.example.bluetooth.bean.BleDeviceBean;
import com.example.bluetooth.callback.BleCallback;
import com.example.bluetooth.intel.BleListener;
import com.example.bluetooth.intel.ConnectListener;
import com.example.bluetooth.intel.DiscoveredListener;
import com.example.bluetooth.intel.NotifyListener;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.TimerTask;/*** @des: 蓝牙管理类* @date: 2024/9/2* @author: yanghaifeng*/
public class BleAdmin implements ConnectListener, NotifyListener {private final static String TAG = "BleAdmin";private static BleAdmin bleAdmin;private BluetoothAdapter mBluetoothAdapter;private BluetoothGatt mOperateGatt;private BluetoothGattService mOperateService;private BluetoothGattCharacteristic mNotifyCharacteristic;private BluetoothGattCharacteristic mWriteCharacteristic;private int mScanCount = 0; // 扫描次数//默认扫描BLE蓝牙时长10s 可更改private final Long mScanTime = 10 * 1000L;private final List<ScanFilter> mScanFilterList = new ArrayList<>();private final ScanSettings mScanSettings = new ScanSettings.Builder().build();private BleCurrentInfo mBleCurrentInfo = new BleCurrentInfo();  // 已保存的蓝牙设备信息private List<BluetoothGattCharacteristic> mBleGattCharacteristics = new ArrayList<>();private long mStartTime = 0L;private boolean mIsFirstConnect = true;private DiscoveredListener mDiscoveredListener = null;private BleListener mBleListener = null;private Thread mHeartThread = null; // 心跳线程private boolean mIsHeart = false; // 心跳线程运行状态private long mHeartOverTime = 3000L; // 心跳超时时间private long mHeartTime = 0L;private Thread mConnectThread = null; // 重连线程private boolean mIsRunning = false; // 重连线程运行状态long mConnectOverTime = 20 * 1000L; // 重连扫描超时时间public static BleAdmin getInstance() {if (bleAdmin == null) {bleAdmin = new BleAdmin();}return bleAdmin;}public BleAdmin() {BluetoothManager bluetoothManager = (BluetoothManager) Utils.getApp().getSystemService(Context.BLUETOOTH_SERVICE);mBluetoothAdapter = bluetoothManager.getAdapter();BleCallback.instance.setConnectListener(this);BleCallback.instance.setNotifyListener(this);}public BluetoothGatt getOperateGatt() {return mOperateGatt;}public void setOperateGatt(BluetoothGatt mOperateGatt) {this.mOperateGatt = mOperateGatt;}public BluetoothGattService getOperateService() {return mOperateService;}public void setOperateService(BluetoothGattService mOperateService) {this.mOperateService = mOperateService;}public BluetoothGattCharacteristic getNotifyCharacteristic() {return mNotifyCharacteristic;}public void setNotifyCharacteristic(BluetoothGattCharacteristic mNotifyCharacteristic) {this.mNotifyCharacteristic = mNotifyCharacteristic;}public BluetoothGattCharacteristic getWriteCharacteristic() {return mWriteCharacteristic;}public void setWriteCharacteristic(BluetoothGattCharacteristic mWriteCharacteristic) {this.mWriteCharacteristic = mWriteCharacteristic;}public BleCurrentInfo getBleCurrentInfo() {return mBleCurrentInfo;}public void setBleCurrentInfo(BleCurrentInfo mBleCurrentInfo) {this.mBleCurrentInfo = mBleCurrentInfo;}public void setDiscoveredListener(DiscoveredListener mDiscoveredListener) {this.mDiscoveredListener = mDiscoveredListener;}public void setBleListener(BleListener mBleListener) {this.mBleListener = mBleListener;}public long getStartTime() {return mStartTime;}public void setStartTime(long mStartTime) {this.mStartTime = mStartTime;}public void setIsFirstConnect(boolean mIsFirstConnect) {this.mIsFirstConnect = mIsFirstConnect;}/*** 开启心跳线程*/public void startHeartThread() {Log.i(TAG, "开启心跳线程");getHeardThread();mHeartThread.start();}private void getHeardThread() {stopHeartThread();mIsHeart = true;mHeartThread = new Thread(new Runnable() {@Overridepublic void run() {mHeartTime = System.currentTimeMillis();while (mIsHeart) {if (System.currentTimeMillis() - mHeartTime > mHeartOverTime) {Log.i(TAG, "心跳超时");disConnectGatt(true);}}}});}/*** 关闭心跳线程*/public void stopHeartThread() {mIsHeart = false;try {if (mHeartThread != null) {mHeartThread.join();mHeartThread = null;}} catch (InterruptedException i) {Log.i(TAG, i.toString());}}/*** 开启重连线程*/public void startConnectThread() {Log.i(TAG, "开启重连线程");getConnectThread();mConnectThread.start();}private void getConnectThread() {mIsHeart = false;stopConnectThread();mIsRunning = true;mConnectThread = new Thread(new Runnable() {@Overridepublic void run() {startScanBleTask();long time = System.currentTimeMillis();Log.i(TAG, "开始尝试重连");while (mIsRunning && System.currentTimeMillis() - time < mConnectOverTime) {List<BleDeviceBean> beans = new ArrayList<>(mBleCurrentInfo.getBleList());for (BleDeviceBean bean: beans) {try {if (Objects.equals(bean.getBleAddress(), mBleCurrentInfo.getBleAddress())) {Log.i(TAG, "找到重连address:" + mBleCurrentInfo.getBleAddress());int count = 0;Log.i(TAG, "第${count + 1}次尝试连接");connectGatt(getRemoteDevice(bean.getBleAddress()));count++;long currentTime = System.currentTimeMillis();while (count < 3) {if (!mIsRunning) {return;}if (mBleCurrentInfo.isConnect()) {break;}if (System.currentTimeMillis() - currentTime < 5000) {continue;}Log.i(TAG, "第${count + 1}次尝试连接");connectGatt(getRemoteDevice(bean.getBleAddress()));currentTime = System.currentTimeMillis();count++;}mIsRunning = false;break;}} catch (Exception e) {Log.i(TAG, e.toString());}}}}});}/*** 关闭重连线程*/public void stopConnectThread() {mIsRunning = false;try {if (mConnectThread != null) {mConnectThread.join();mConnectThread = null;}} catch (InterruptedException i) {Log.i(TAG, i.toString());} finally {stopScanBle();}}/*** 开启手机蓝牙**/@SuppressLint("MissingPermission")public boolean openBlueTooth() {return mBluetoothAdapter.enable();}/*** 获取蓝牙状态**/public boolean getBleEnable() {return mBluetoothAdapter.isEnabled();}/*** 关闭手机蓝牙**/@SuppressLint("MissingPermission")public void closeBlueTooth() {mBluetoothAdapter.disable();}/*** 获取外围设备广播信息** @param address 外围设备ble地址Mac* @return 通过MAC获取到的外围设备*/public BluetoothDevice getRemoteDevice(String address) {return mBluetoothAdapter.getRemoteDevice(address);}/*** 识别特征功能** @param characteristic service中的特征* @return*/public String detectCharacteristic(BluetoothGattCharacteristic characteristic) {StringBuilder propSb = new StringBuilder();if (characteristic.getProperties() > 0 &&  BluetoothGattCharacteristic.PROPERTY_READ > 0) {propSb.append("Read");}if (characteristic.getProperties() > 0 && BluetoothGattCharacteristic.PROPERTY_WRITE > 0) {if (propSb.length() > 0) {propSb.append("   ");}propSb.append("Write");}if (characteristic.getProperties() > 0 && BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE > 0) {if (propSb.length() > 0) {propSb.append("   ");}propSb.append("Write No Response");}if (characteristic.getProperties() > 0 && BluetoothGattCharacteristic.PROPERTY_NOTIFY > 0) {if (propSb.length() > 0) {propSb.append("   ");}propSb.append("Notify");}if (characteristic.getProperties() > 0 && BluetoothGattCharacteristic.PROPERTY_INDICATE > 0) {if (propSb.length() > 0) {propSb.append("   ");}propSb.append("Indicate");}return propSb.toString();}/*** 蓝牙扫描*/@SuppressLint("MissingPermission")public void startScanBle() {if (!getBleEnable()) {
//            openBlueTooth();TimerUtil.cancelTimer();HandlerUtil.post(()-> ToastUtil.showToast("蓝牙未打开", 2000));return;}if (!GpsAdmin.getInstance().isGpsOn()) {TimerUtil.cancelTimer();HandlerUtil.post(()-> ToastUtil.showToast("定位未打开", 2000));return;}Log.i(TAG, "开始扫描蓝牙");mBleCurrentInfo.clearBleResult();BleCallback.instance.startLeScan(mBluetoothAdapter, new ScanCallback() {@Overridepublic void onScanResult(int callbackType, ScanResult result) {super.onScanResult(callbackType, result);try {if (result.getDevice() == null || result.getDevice().getAddress() == null) {return;}if (result.getDevice().getName() == null || Objects.equals(result.getDevice().getName(), "")) {return;}for (BleDeviceBean bean : mBleCurrentInfo.getBleList()) {if (Objects.equals(bean.getBleAddress(), result.getDevice().getAddress())) {return;}}Log.i(TAG, "NAME:" + result.getDevice().getName() + ",ADDRESS:" + result.getDevice().getAddress());BleDeviceBean bean = new BleDeviceBean(result.getDevice().getName(), result.getDevice().getAddress(), false);mBleCurrentInfo.addBleResult(bean);} catch (Exception e) {Log.i(TAG, e.toString());}}@Overridepublic void onBatchScanResults(List<ScanResult> results) {super.onBatchScanResults(results);}@Overridepublic void onScanFailed(int errorCode) {super.onScanFailed(errorCode);}}, mScanTime, mScanFilterList, mScanSettings);}public void startScanBleTask() {stopScanBle();mBleCurrentInfo.clearBleResult();mScanCount = 0;TimerUtil.startTask(new TimerTask() {@Overridepublic void run() {if (!mBleCurrentInfo.getBleList().isEmpty() || mScanCount > 0) {TimerUtil.cancelTimer();return;}startScanBle();mScanCount++;}}, 0L, 3000L);}/*** 开始扫描蓝牙*/public void startScanBleFirst() {if (!mBleCurrentInfo.getHisBleList().isEmpty()) {mStartTime = System.currentTimeMillis();startScanBleTask();}}@SuppressLint("MissingPermission")public void stopScanBle() {TimerUtil.cancelTimer();BleCallback.instance.stopScan(mBluetoothAdapter);}/*** 连接GATT*/public void connectGatt(BluetoothDevice bluetoothDevice) {BleCallback.instance.connect(false, bluetoothDevice);}/*** 断开GATT*/@SuppressLint("MissingPermission")public void disConnectGatt(boolean isReconnect) {Log.i(TAG, "断开GATT,是否重连:"  + isReconnect);try {if (mBleCurrentInfo.isConnect()) {if (mOperateGatt != null) {unsubscribeNotify();mOperateGatt.close();}}} catch (Exception e) {Log.i(TAG, e.toString());} finally {for (BleDeviceBean bean: mBleCurrentInfo.getBleList()) {bean.setBleConnect(false);}mBleCurrentInfo.setConnect(false);mOperateGatt = null;mNotifyCharacteristic = null;mWriteCharacteristic = null;if (mBleListener != null) {mBleListener.onConnectSuccess(false);}mIsHeart = false;if (isReconnect) {startConnectThread();}}}/*** 发送消息*/@SuppressLint("MissingPermission")public void sendMessage(byte[] message) {if (!mBleCurrentInfo.isConnect()) {ToastUtil.showToast("蓝牙未连接,请连接蓝牙!", 2000);return;}if (mOperateGatt == null || mWriteCharacteristic == null) {Log.i(TAG, "发送消息失败,未持有相关服务");return;}mWriteCharacteristic.setValue(message);mOperateGatt.writeCharacteristic(mWriteCharacteristic);}public void subscribeNotify() {if (mOperateGatt != null && mNotifyCharacteristic != null) {BleCallback.instance.subscribeNotify(mOperateGatt,mNotifyCharacteristic,UsedUUID.NOTIFY_DESCRIPTOR_UUID);} else {Log.i(TAG, "绑定失败,未持有相关服务");ToastUtil.showToast("绑定失败", 2000);}}public void unsubscribeNotify() {if (mOperateGatt != null && mNotifyCharacteristic != null) {BleCallback.instance.unsubscribeNotify(mOperateGatt, mNotifyCharacteristic, UsedUUID.NOTIFY_DESCRIPTOR_UUID);} else {Log.i(TAG, "解绑失败,未持有相关服务");}}/*** GATT连接成功*/@Overridepublic void onConnectSuccess(BluetoothGatt gatt) {Log.i(TAG, "GATT连接成功");}/*** GATT断开连接*/@Overridepublic void onDisconnect(BluetoothGatt gatt) {if (mOperateGatt != null || !Objects.equals(gatt.getDevice().getAddress(), mOperateGatt.getDevice().getAddress())) {return;}Log.i(TAG, "GATT断开连接");boolean isReconnect = false;if (!mBleCurrentInfo.getBleList().isEmpty() && Objects.equals(gatt.getDevice().getAddress(), mBleCurrentInfo.getBleList().get(0).getBleAddress())) {isReconnect = true;}disConnectGatt(isReconnect);}/*** 发现服务*/@SuppressLint("MissingPermission")@Overridepublic void onServicesDiscovered(BluetoothGatt gatt, List<BluetoothGattService> services) {Log.i(TAG, "发现服务UUID");boolean isNext = false;for (BluetoothGattService service: services) {if (service.getUuid().toString().equals(UsedUUID.SERVICE_UUID.toString())) {Log.i(TAG, "找到SERVICE_UUID");mOperateService = service;mBleGattCharacteristics.clear();mBleGattCharacteristics.addAll(service.getCharacteristics());isNext = true;break;}}if (!isNext) {Log.i(TAG, "未找到指定的服务UUID");disConnectGatt(false);return;}isNext = false;List<BluetoothGattCharacteristic> bleGattCharacteristics = new ArrayList<>(mBleGattCharacteristics);for (BluetoothGattCharacteristic bluetoothGattCharacteristic: bleGattCharacteristics) {if (bluetoothGattCharacteristic.getUuid().toString().equals(UsedUUID.NOTIFY_CHARACTERISTIC_UUID.toString())) {Log.i(TAG, "找到NOTIFY_CHARACTERISTIC_UUID");mNotifyCharacteristic = bluetoothGattCharacteristic;isNext = true;}if (bluetoothGattCharacteristic.getUuid().toString().equals(UsedUUID.WRITE_CHARACTERISTIC_UUID.toString())) {Log.i(TAG, "找到WRITE_CHARACTERISTIC_UUID");mWriteCharacteristic = bluetoothGattCharacteristic;}}if (isNext) {try {Log.i(TAG, "BLE蓝牙连接成功");if (mBleCurrentInfo.isConnect()) {mBleCurrentInfo.setConnect(false);mOperateGatt.close();}for (BleDeviceBean bean: mBleCurrentInfo.getBleList()) {bean.setBleConnect(false);}mOperateGatt = gatt;subscribeNotify();Log.i(TAG, "BLE蓝牙绑定成功");mBleCurrentInfo.setConnect(true);if (mIsFirstConnect) {mBleCurrentInfo.getBleList().get(0).setBleConnect(true);}
//                if (Objects.equals(mBleCurrentInfo.getBleAddress(), gatt.getDevice().getAddress()) && !mBleCurrentInfo.getBleList().isEmpty()) {
//                    mBleCurrentInfo.getBleList().get(0).setBleConnect(true);
//                }mBleCurrentInfo.setConnect(true);mBleCurrentInfo.setBleName(gatt.getDevice().getName());mBleCurrentInfo.setBleAddress(gatt.getDevice().getAddress());mBleCurrentInfo.updateScanBean();if (mDiscoveredListener != null) {mDiscoveredListener.onServicesDiscovered(gatt);}if (mBleListener != null) {mBleListener.onConnectSuccess(true);}} catch (Exception e) {Log.i(TAG, "BLE蓝牙连接异常");disConnectGatt(false);}} else {disConnectGatt(false);}}@Overridepublic void onServicesDiscoverFailed(int status) {Log.i(TAG, "onServicesDiscoverFailed");}@Overridepublic void onServicesChange(BluetoothGatt gatt) {Log.i(TAG, "onServicesChange");}@Overridepublic void onCharacteristicChange(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {mHeartTime = System.currentTimeMillis();Log.i(TAG, Arrays.toString(characteristic.getValue()));mBleListener.onCharacteristicChange(characteristic.getValue());}
}

该蓝牙管理类中采用了心跳来保证蓝牙通讯的稳定性,每当超时未接收到外围设备心跳时,都会开启重连线程,若没有相关心跳协议,可将心跳部分代码去掉。

② BleCallback完整代码:

package com.example.bluetooth.callback;import android.annotation.SuppressLint;
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.BluetoothProfile;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanSettings;
import android.util.Log;import androidx.annotation.NonNull;
import androidx.annotation.RequiresPermission;import com.example.bluetooth.bean.BleDevice;
import com.example.bluetooth.intel.CharacteristicListener;
import com.example.bluetooth.intel.ConnectListener;
import com.example.bluetooth.intel.DescriptorListener;
import com.example.bluetooth.intel.MtuListener;
import com.example.bluetooth.intel.NotifyListener;
import com.example.bluetooth.intel.PhyListener;
import com.example.bluetooth.intel.ReliableListener;
import com.example.bluetooth.intel.RssiListener;
import com.example.bluetooth.util.TimerUtil;
import com.example.bluetooth.util.Utils;import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.TimerTask;
import java.util.UUID;/*** @des: 蓝牙回调类* @date: 2024/9/4* @author: yanghaifeng*/
public class BleCallback extends BluetoothGattCallback {private final static String TAG = "BleCallback";public static BleCallback instance = new BleCallback();private ArrayList<BleDevice> mConnectedBleDeviceList = new ArrayList<>();private ConnectListener mConnectListener = null;private MtuListener mMtuListener = null;private RssiListener mRssiListener = null;private NotifyListener mNotifyListener = null;private CharacteristicListener mCharacteristicListener = null;private PhyListener mPhyListener = null;private ReliableListener mReliableListener = null;private DescriptorListener mDescriptorListener = null;private ScanCallback mScanCallback = null;public ArrayList<BleDevice> getConnectedBleDeviceList() {return mConnectedBleDeviceList;}public void setConnectedBleDeviceList(ArrayList<BleDevice> mConnectedBleDeviceList) {this.mConnectedBleDeviceList = mConnectedBleDeviceList;}public ConnectListener getConnectListener() {return mConnectListener;}public void setConnectListener(ConnectListener mConnectListener) {this.mConnectListener = mConnectListener;}public MtuListener getMtuListener() {return mMtuListener;}public void setMtuListener(MtuListener mMtuListener) {this.mMtuListener = mMtuListener;}public RssiListener getRssiListener() {return mRssiListener;}public void setRssiListener(RssiListener mRssiListener) {this.mRssiListener = mRssiListener;}public NotifyListener getNotifyListener() {return mNotifyListener;}public void setNotifyListener(NotifyListener mNotifyListener) {this.mNotifyListener = mNotifyListener;}public CharacteristicListener getCharacteristicListener() {return mCharacteristicListener;}public void setCharacteristicListener(CharacteristicListener mCharacteristicListener) {this.mCharacteristicListener = mCharacteristicListener;}public PhyListener getPhyListener() {return mPhyListener;}public void setPhyListener(PhyListener mPhyListener) {this.mPhyListener = mPhyListener;}public ReliableListener getReliableListener() {return mReliableListener;}public void setReliableListener(ReliableListener mReliableListener) {this.mReliableListener = mReliableListener;}public DescriptorListener getDescriptorListener() {return mDescriptorListener;}public void setDescriptorListener(DescriptorListener mDescriptorListener) {this.mDescriptorListener = mDescriptorListener;}public ScanCallback getScanCallback() {return mScanCallback;}public void setScanCallback(ScanCallback mScanCallback) {this.mScanCallback = mScanCallback;}/*** 开启ble扫描*/@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)public void startLeScan(BluetoothAdapter bluetoothAdapter,ScanCallback scanCallback,Long scanTime,List<ScanFilter> filters,ScanSettings scanSettings) {try {if (bluetoothAdapter == null) {return;}if (scanCallback == null) {return;}BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();if (bluetoothLeScanner == null) {return;}if (mScanCallback != null && bluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {bluetoothLeScanner.stopScan(mScanCallback);mScanCallback = null;}TimerUtil.cancelDialogTimer();mScanCallback = scanCallback;bluetoothLeScanner.startScan(filters,scanSettings,mScanCallback);TimerUtil.startDialogTask(new TimerTask() {@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)@Overridepublic void run() {if (mScanCallback != null && bluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {bluetoothLeScanner.stopScan(mScanCallback);mScanCallback = null;}}}, scanTime);} catch (Exception e) {Log.i(TAG, "蓝牙扫描异常");}}@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)public void stopScan(BluetoothAdapter bluetoothAdapter) {if (bluetoothAdapter.getBluetoothLeScanner() != null && mScanCallback != null&& bluetoothAdapter.getState() == BluetoothAdapter.STATE_ON) {bluetoothAdapter.getBluetoothLeScanner().stopScan(mScanCallback);}}@SuppressLint("MissingPermission")public void disconnect(BluetoothGatt bluetoothGatt) {bluetoothGatt.disconnect();}@SuppressLint("MissingPermission")public void connect(Boolean isAutoConnect,BluetoothDevice bluetoothDevice) {bluetoothDevice.connectGatt(Utils.getApp(), isAutoConnect, this, BluetoothDevice.TRANSPORT_LE);}@SuppressLint("MissingPermission")public void subscribeNotify(BluetoothGatt bleGatt,BluetoothGattCharacteristic characteristic,UUID descriptorUUID) {bleGatt.setCharacteristicNotification(characteristic, true);BluetoothGattDescriptor clientConfig = characteristic.getDescriptor(descriptorUUID);if (clientConfig != null) {clientConfig.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);bleGatt.writeDescriptor(clientConfig);}}@SuppressLint("MissingPermission")public void unsubscribeNotify(BluetoothGatt bleGatt,BluetoothGattCharacteristic characteristic,UUID descriptorUUID) {try {bleGatt.setCharacteristicNotification(characteristic, false);BluetoothGattDescriptor clientConfig = characteristic.getDescriptor(descriptorUUID);if (clientConfig != null) {clientConfig.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);bleGatt.writeDescriptor(clientConfig);}} catch (Exception e) {Log.i(TAG, e.toString());}}@Overridepublic void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {if (mPhyListener != null) {mPhyListener.onPhyUpdate(gatt, txPhy, rxPhy, status);}}@Overridepublic void onPhyRead(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {if (mPhyListener != null) {mPhyListener.onPhyRead(gatt, txPhy, rxPhy, status);}}@SuppressLint("MissingPermission")@Overridepublic void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {Log.i(TAG, "onConnectionStateChange,status:" + status +",newState:" + newState);switch (newState) {case BluetoothProfile.STATE_CONNECTED:BleDevice bleDevice = new BleDevice();bleDevice.setDeviceName(gatt.getDevice().getName());bleDevice.setMacAddress(gatt.getDevice().getAddress());bleDevice.setGatt(gatt);mConnectedBleDeviceList.add(bleDevice);Log.i(TAG, gatt.getDevice().getAddress() + "已连接");if (mConnectListener != null) {mConnectListener.onConnectSuccess(gatt);}try {Thread.sleep(50);} catch (InterruptedException e) {Log.i(TAG, e.toString());}gatt.discoverServices();break;case BluetoothProfile.STATE_DISCONNECTED:Iterator<BleDevice> iterator = mConnectedBleDeviceList.iterator();while (iterator.hasNext()) {BleDevice next = iterator.next();if (Objects.equals(next.getMacAddress(), gatt.getDevice().getAddress())) {iterator.remove();}}Log.i(TAG, gatt.getDevice().getAddress() + "断开连接");if (mConnectListener != null) {mConnectListener.onDisconnect(gatt);}gatt.close();break;default:break;}}@Overridepublic void onServicesDiscovered(BluetoothGatt gatt, int status) {Log.i(TAG, "onServicesDiscovered,status:" + status);switch (status) {case BluetoothGatt.GATT_SUCCESS:if (!mConnectedBleDeviceList.isEmpty()) {mConnectedBleDeviceList.get(mConnectedBleDeviceList.size() - 1).setServiceList(gatt.getServices());if (mConnectListener != null) {mConnectListener.onServicesDiscovered(gatt, gatt.getServices());}}break;default:if (mConnectListener != null) {mConnectListener.onServicesDiscoverFailed(status);}break;}}@Overridepublic void onCharacteristicRead(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic,int status) {if (mCharacteristicListener != null) {mCharacteristicListener.onCharacteristicRead(gatt, characteristic, status);}}@Overridepublic void onCharacteristicWrite(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic,int status) {if (mCharacteristicListener != null) {mCharacteristicListener.onCharacteristicWrite(gatt, characteristic, status);}}@Overridepublic void onCharacteristicChanged(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic) {if (mNotifyListener != null) {mNotifyListener.onCharacteristicChange(gatt, characteristic);}}@Overridepublic void onDescriptorRead(BluetoothGatt gatt,BluetoothGattDescriptor descriptor,int status) {if (mDescriptorListener != null) {mDescriptorListener.onDescriptorRead(gatt, descriptor, status);}}@Overridepublic void onDescriptorWrite(BluetoothGatt gatt,BluetoothGattDescriptor descriptor,int status) {if (mDescriptorListener != null) {mDescriptorListener.onDescriptorWrite(gatt, descriptor, status);}}@Overridepublic void onReliableWriteCompleted(BluetoothGatt gatt, int status) {if (mReliableListener != null) {mReliableListener.onReliableWriteCompleted(gatt, status);}}@Overridepublic void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {if (mRssiListener != null) {mRssiListener.onReadRemoteRssi(gatt, rssi, status);}}@SuppressLint("MissingPermission")@Overridepublic void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {gatt.discoverServices();if (mMtuListener != null) {mMtuListener.onMtuChange(gatt, mtu, status);}}@Overridepublic void onServiceChanged(@NonNull BluetoothGatt gatt) {if (mConnectListener != null) {mConnectListener.onServicesChange(gatt);}}
}

遇到的问题
1.
问题:
  蓝牙扫描有时候会发现不了设备,多次调用BluetoothLeScanner.startScan后可正常找到。
解决方案:
  为了友好型考虑,开启一个循环Timer,每3秒开启一次蓝牙扫描,如找到设备或扫描次数超过3次,则停止循环。

    private int mScanCount = 0; // 扫描次数public void startScanBleTask() {stopScanBle();mBleCurrentInfo.clearBleResult();mScanCount = 0;TimerUtil.startTask(new TimerTask() {@Overridepublic void run() {if (!mBleCurrentInfo.getBleList().isEmpty() || mScanCount > 0) {TimerUtil.cancelTimer();return;}startScanBle();mScanCount++;}}, 0L, 3000L);}

问题:
  蓝牙连接一段时间后,中心设备Notify属性接收不到外围设备发送的消息,即便断线重连也无法再次受到。
解决方案:
   断开设备后,进行重新扫描,再次连接时,可接收到Notify的消息。若设备需保持长期通讯,可与外围设备建立心跳协议,当中心设备超时未接收到心跳时,进行断线
->扫描->连接操作。

	private Thread mHeartThread = null; // 心跳线程private boolean mIsHeart = false; // 心跳线程运行状态private long mHeartOverTime = 3000L; // 心跳超时时间private long mHeartTime = 0L;private Thread mConnectThread = null; // 重连线程private boolean mIsRunning = false; // 重连线程运行状态long mConnectOverTime = 20 * 1000L; // 重连扫描超时时间/*** 开启心跳线程*/public void startHeartThread() {Log.i(TAG, "开启心跳线程");getHeardThread();mHeartThread.start();}private void getHeardThread() {stopHeartThread();mIsHeart = true;mHeartThread = new Thread(new Runnable() {@Overridepublic void run() {mHeartTime = System.currentTimeMillis();while (mIsHeart) {if (System.currentTimeMillis() - mHeartTime > mHeartOverTime) {Log.i(TAG, "心跳超时");disConnectGatt(true);}}}});}/*** 关闭心跳线程*/public void stopHeartThread() {mIsHeart = false;try {if (mHeartThread != null) {mHeartThread.join();mHeartThread = null;}} catch (InterruptedException i) {Log.i(TAG, i.toString());}}/*** 开启重连线程*/public void startConnectThread() {Log.i(TAG, "开启重连线程");getConnectThread();mConnectThread.start();}private void getConnectThread() {mIsHeart = false;stopConnectThread();mIsRunning = true;mConnectThread = new Thread(new Runnable() {@Overridepublic void run() {startScanBleTask();long time = System.currentTimeMillis();Log.i(TAG, "开始尝试重连");while (mIsRunning && System.currentTimeMillis() - time < mConnectOverTime) {List<BleDeviceBean> beans = new ArrayList<>(mBleCurrentInfo.getBleList());for (BleDeviceBean bean: beans) {try {if (Objects.equals(bean.getBleAddress(), mBleCurrentInfo.getBleAddress())) {Log.i(TAG, "找到重连address:" + mBleCurrentInfo.getBleAddress());int count = 0;Log.i(TAG, "第${count + 1}次尝试连接");connectGatt(getRemoteDevice(bean.getBleAddress()));count++;long currentTime = System.currentTimeMillis();while (count < 3) {if (!mIsRunning) {return;}if (mBleCurrentInfo.isConnect()) {break;}if (System.currentTimeMillis() - currentTime < 5000) {continue;}Log.i(TAG, "第${count + 1}次尝试连接");connectGatt(getRemoteDevice(bean.getBleAddress()));currentTime = System.currentTimeMillis();count++;}mIsRunning = false;break;}} catch (Exception e) {Log.i(TAG, e.toString());}}}}});}/*** 关闭重连线程*/public void stopConnectThread() {mIsRunning = false;try {if (mConnectThread != null) {mConnectThread.join();mConnectThread = null;}} catch (InterruptedException i) {Log.i(TAG, i.toString());} finally {stopScanBle();}}

蓝牙测试demo

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/53875.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华为OD机试 - 推荐多样性(Python/JS/C/C++ 2024 E卷 100分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试真题&#xff08;Python/JS/C/C&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加入华为OD刷题交流群&#xff0c;…

气压测试实验(用IIC)

I2C: 如果没有I2c这类总线&#xff0c;连接方法可能会如下图&#xff1a; 单片机所有的通讯协议&#xff0c;无非是建立在引脚&#xff08;高低电平的变换高低电平持续的时间&#xff09;这二者的组合上&#xff0c;i2c 多了一个clock线&#xff0c;负责为数据传输打节拍。 (i2…

同时拥有独显和核显,怎么让应用程序选择使用哪个GPU?

看你现在使用的是核显还是独显 勾选上GPU引擎选项&#xff0c;后面便会标识你所使用的是哪种显卡&#xff0c;如果是独立显卡&#xff0c;就可以免去后续的操作&#xff1b;如果不是&#xff0c;那么请继续接下来的操作。 将你需要使用独显的程序换成gpu1&#xff08;独显&am…

佰朔资本:未来钢铁行业产业格局有望稳中趋好

组织指出&#xff0c;未来钢铁作业工业格式有望稳中趋好&#xff0c;叠加当时部分公司已经处于价值小看区域&#xff0c;现阶段仍具结构性出资机会&#xff0c;尤其是拥有较高毛利率水平的优特钢企业和本钱管控力度强、具有规划效应的龙头钢企&#xff0c;未来存在估值修改的机…

JavaEE:文件操作

文章目录 文件操作和IO文件系统操作File介绍属性构造方法方法 代码演示前四个listmkdirrenameTo 文件操作和IO 文件系统操作 创建文件,删除文件,创建目录,重命名… Java中有一个类,可以帮我们完成上述操作. 这个类叫做File类. File介绍 属性 这个表格描述了文件路径的分隔符…

应急响应实战---是谁修改了我的密码?

前言&#xff1a;此次应急响应为真实案例&#xff0c;客户反馈无法通过密码登录服务器&#xff0c;疑似服务器被入侵 0x01 如何找回密码&#xff1f; 客户服务器为windows server2019&#xff0c;运维平台为PVE平台&#xff1b;实际上无论是windows系统或者是linux系统&#…

个人学习笔记7-5:动手学深度学习pytorch版-李沐

#人工智能# #深度学习# #语义分割# #计算机视觉# #神经网络# 计算机视觉 13.10 转置卷积 例如&#xff0c;卷积层和汇聚层&#xff0c;通常会减少下采样输入图像的空间维度&#xff08;高和宽&#xff09;。然而如果输入和输出图像的空间维度相同&#xff0c;在以像素级分类…

【物联网技术大作业】设计一个智能家居的应用场景

前言&#xff1a; 本人的物联网技术的期末大作业&#xff0c;希望对你有帮助。 目录 大作业设计题 &#xff08;1&#xff09;智能家居的概述。 &#xff08;2&#xff09;介绍智能家居应用。要求至少5个方面的应用&#xff0c;包括每个应用所采用的设备&#xff0c;性能&am…

2023级JavaScript与jQuery

第三课&#xff1a;JavaScript对象编程 一.预习笔记 1.Date对象 对象创建&#xff1a;var myDatenew Date() 输出显示当前日期的标准时间 对象创建&#xff1a;var myDatenew Date(“2024/09/14”) 对象创建&#xff1a;var myDatenew Date(2024,9,14) 当前对象创建时&…

TiDB从0到1学习笔记(精华篇)

历时四个月&#xff0c;恭喜赵老师的《TiDB从0到1》 系列文章顺利完结&#xff0c;小编再次梳理一遍文稿&#xff0c;并附注解分享给大家。 整体架构 从 TiDB 1.0 到 8.0&#xff0c;TiDB 的体系结构一直在不断演进。接下来让我们一起看看整体架构的变化。 TiDB v1 TiDB v1&…

Windows 环境下 vscode 配置 C/C++ 环境

vscode Visual Studio Code&#xff08;简称 VSCode&#xff09;是一个由微软开发的免费、开源的代码编辑器。它支持多种编程语言&#xff0c;并提供了代码高亮、智能代码补全、代码重构、调试等功能&#xff0c;非常适合开发者使用。VSCode 通过安装扩展&#xff08;Extension…

node.js实现阿里云短信发送

效果图 实现 一、准备工作 1、官网直达网址&#xff1a; 阿里云 - 短信服务 2、按照首页提示依次完成相应资质认证和短信模板审核&#xff1b; 3、获取你的accessKeySecret和accessKeyId&#xff1b; 方法如下&#xff1a; 获取AccessKey-阿里云帮助中心 4、获取SignNa…

【LabVIEW学习篇 - 24】:生产者/消费者设计模式

文章目录 生产者/消费者设计模式案例&#xff1a;控制LED等亮灭 生产者/消费者设计模式 生产者/消费者是多线程编程中最基本的一种模式&#xff0c;使用非常普遍。从软件角度看&#xff0c;生产者就是数据的提供方&#xff0c;而消费者就是数据的消费处理方&#xff0c;二者之…

微信小程序开发——比较两个数字大小

在这里我们使用的工具是 需要自行安装和配置。 在微信小程序中比较两个数字大小有以下几种方式&#xff1a; 一、普通条件判断 在小程序的.js 文件中&#xff0c;先定义两个数字&#xff0c;如let num1 5; let num2 3;。通过if - else if - else语句&#xff0c;根据num1与…

文件管理系统DCC与泛微OA系统集成案例

一、项目背景 上海某半导体有限公司主要产品应用于图像传感器、 图像信号处理芯片、 低功耗芯片、 射频芯片等。 公司内部有DCC文件管理系统和OA系统&#xff0c;由SAP PO平台进行中间管理&#xff0c;DCC系统对接泛微OA系统推送文件等操作&#xff0c;提高公司内部各自系统…

智能智造和工业软件研发平台SCSAI功能介绍

用爱编程30年&#xff0c;倾心打造工业和智能智造软件研发平台SCIOT,用创新的方案、大幅的让利和极致的营销&#xff0c;致力于为10000家的中小企业实现数字化转型&#xff0c;打造数字化企业和智能工厂&#xff0c;点击上边蓝色字体&#xff0c;关注“AI智造AI编程”或文末扫码…

lightdm , xrandr , startx 桌面管理器,窗口管理器

问题&#xff1a; 了解这几个的含义。 显示服务器 这个不是很明白 显示管理器&#xff0c; 知道就行了&#xff0c;也不是很明白。 窗口管理器。 桌面管理器。 这个其实就是 桌面环境了&#xff0c; 我们的板卡上使用的是xface 。 这个 xface 是一个集合&#xff0c;这里面…

亚马逊IP关联及其解决方案

在电子商务领域&#xff0c;亚马逊作为全球领先的在线购物平台&#xff0c;吸引了众多商家和个人的参与。然而&#xff0c;随着业务规模的扩大&#xff0c;商家在使用亚马逊服务时可能会遇到IP关联的问题&#xff0c;这不仅影响账户的正常运营&#xff0c;还可能带来一系列不利…

基于SpringBoot+Vue的个性化视频推荐系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSpringBootVueMySQL的…

ComfyUI安装节点过程中被降低了版本的软件包重新安装

最近在安装2个没怎么及时更新节点时&#xff0c;安装节点依赖性过程中&#xff0c;将原高版本的软件包&#xff0c;给降到了低版本&#xff0c;解决的办法就是&#xff1a;1、再次删除软件包&#xff0c;2、指定版本号重新安装回高版本软件包。