Android -BLE 蓝牙模块开发

news2024/10/7 4:26:49

Android-Ble蓝牙开发Demo示例–扫描,连接,发送和接收数据,分包解包(附源码) - 简书前言 万物互联的物联网时代的已经来临,ble蓝牙开发在其中扮演着举重若轻的角色。最近刚好闲一点,抽时间梳理下这块的知识点。 涉及ble蓝牙通讯的客户端(开启、扫描、连接、发送...icon-default.png?t=N7T8https://www.jianshu.com/p/1586f916f3d0涉及ble蓝牙通讯的客户端(开启、扫描、连接、发送和接收数据、分包解包)和服务端(初始化广播数据、开始广播、配置Services、Server回调操作)整个环节以及一些常见的问题即踩过的一些坑。

蓝牙有传统(经典)蓝牙和低功耗蓝牙BLE(Bluetooth Low Energy)之分,两者的开发的API不一样,本文主讲Ble蓝牙开发,传统蓝牙不展开,有需要的可以自行了解。

相对传统蓝牙,BLE低功耗蓝牙,主要特点是快速搜索,快速连接,超低功耗保持连接和数据传输。

ndroid4.3(API Level 18)开始引入BLE的核心功能并提供了相应的 API。应用程序通过这些 API 扫描蓝牙设备、查询 services、读写设备的 characteristics(属性特征)等操作。

BLE蓝牙协议是GATT协议, BLE相关类不多, 全都位于android.bluetooth包和android.bluetooth.le包的几个类:
android.bluetooth.
.BluetoothGattService 包含多个Characteristic(属性特征值), 含有唯一的UUID作为标识
.BluetoothGattCharacteristic 包含单个值和多个Descriptor, 含有唯一的UUID作为标识
.BluetoothGattDescriptor 对Characteristic进行描述, 含有唯一的UUID作为标识

.BluetoothGatt 客户端相关
.BluetoothGattCallback 客户端连接回调
.BluetoothGattServer 服务端相关
.BluetoothGattServerCallback 服务端连接回调

android.bluetooth.le.
.AdvertiseCallback 服务端的广播回调
.AdvertiseData 服务端的广播数据
.AdvertiseSettings 服务端的广播设置
.BluetoothLeAdvertiser 服务端的广播

.BluetoothLeScanner 客户端扫描相关(Android5.0新增)
.ScanCallback 客户端扫描回调
.ScanFilter 客户端扫描过滤
.ScanRecord 客户端扫描结果的广播数据
.ScanResult 客户端扫描结果
.ScanSettings 客户端扫描设置

BLE设备分为两种设备: 客户端(也叫主机/中心设备/Central), 服务端(也叫从机/外围设备/peripheral)
客户端的核心类是 BluetoothGatt
服务端的核心类是 BluetoothGattServer 和 BluetoothLeAdvertiser
BLE数据的核心类是 BluetoothGattCharacteristic 和 BluetoothGattDescriptor

下面详细讲解下客户端和服务端的开发步骤流程

一、BLE客户端开发流程

1、申请权限

安卓手机涉及蓝牙权限问题,蓝牙开发需要在AndroidManifest.xml文件中添加权限声明:

<!-- 蓝牙权限 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
为适配安卓6.0以及以上版本需要添加一个模糊定位的权限
 <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
手机权限管理中允许此权限,否则会出现无法搜索到设备的情况;

BLE权限增加了BEL支持检查
(1).在manifest中添加权限
<!-- true 表示手机必须支持BLE,否则无法安装!
    这里设为false, 运行后在Activity中检查-->
<uses-feature
    android:name="android.hardware.bluetooth_le"
    android:required="false" />

(2).在Activity中设置蓝牙
// 检查是否支持BLE蓝牙
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
    Util.toast(this, "本机不支持低功耗蓝牙!");
    finish();
    return;
}

2、打开蓝牙

在搜索设备之前需要询问打开手机蓝牙:

//获取系统蓝牙适配器管理类
private BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// 询问打开蓝牙
if (mBluetoothAdapter != null && !mBluetoothAdapter.isEnabled()) {
        Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enableBtIntent, 1);
}
  // 申请打开蓝牙请求的回调
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    // TODO Auto-generated method stub
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == 1) {
        if (resultCode == RESULT_OK) {
            Toast.makeText(this, "蓝牙已经开启", Toast.LENGTH_SHORT).show();
        } else if (resultCode == RESULT_CANCELED) {
            Toast.makeText(this, "没有蓝牙权限", Toast.LENGTH_SHORT).show();
            finish();
        }
    }
}

3、搜索设备

注意: BLE设备地址是动态变化(每隔一段时间都会变化),而经典蓝牙设备是出厂就固定不变了!

BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// 下面使用Android5.0新增的扫描API,扫描返回的结果更友好,比如BLE广播数据以前是byte[] scanRecord,而新API帮我们解析成ScanRecord类\
final BluetoothLeScanner bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner();
bluetoothLeScanner.startScan(mScanCallback);
mHandler.postDelayed(new Runnable() {
    @Override
    public void run() {
        bluetoothLeScanner.stopScan(mScanCallback); //停止扫描
        isScanning = false;
    }
}, 3000);

// 扫描结果Callback
private final ScanCallback mScanCallback = new ScanCallback() {
    @Override
    public void onScanResult(int callbackType, ScanResult result) {、
        BluetoothDevice dev = result.getDevice() 获取BLE设备信息
        // result.getScanRecord() 获取BLE广播数据
    }
};

// 旧API是BluetoothAdapter.startLeScan(LeScanCallback callback)方式扫描BLE蓝牙设备,如下:
mBluetoothAdapter.startLeScan(callback);
private LeScanCallback callback = new LeScanCallback() {
    @Override
    public void onLeScan(BluetoothDevice device, int arg1, byte[] arg2) {
        //device为扫描到的BLE设备
        if(device.getName() == "目标设备名称"){
            //获取目标设备
            targetDevice = device;
        }
    }
};

4、连接设备

通过扫描BLE设备,根据设备名称区分出目标设备targetDevice,下一步实现与目标设备的连接,在连接设备之前要停止搜索蓝牙;停止搜索一般需要一定的时间来完成,最好调用停止搜索函数之后加以100ms的延时,保证系统能够完全停止搜索蓝牙设备。停止搜索之后启动连接过程;

BLE蓝牙的连接方法相对简单只需调用connectGatt方法;

public BluetoothGatt connectGatt (Context context, boolean autoConnect, BluetoothGattCallback callback);

参数说明

  • 返回值 BluetoothGatt: BLE蓝牙连接管理类,主要负责与设备进行通信;
  • boolean autoConnect:建议置为false,能够提升连接速度;
  • BluetoothGattCallback callback 连接回调,重要参数,BLE通信的核心部分;

5、设备通信

与设备建立连接之后与设备通信,整个通信过程都是在BluetoothGattCallback的异步回调函数中完成;

BluetoothGattCallback中主要回调函数如下:

private BluetoothGattCallback gattCallback = new BluetoothGattCallback() {

    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status,int newState) {
        //连接状态改变的Callback
    }
    
    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        //服务发现成功的Callback
    }
    
    @Override
    public void onCharacteristicWrite(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic, int status) {
        //写入Characteristic
    }
    
    @Override
    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
         //读取Characteristic 
     }

    @Override
    public void onCharacteristicChanged(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic) {
        //通知Characteristic
    }

    @Override
    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
        //写入Descriptor
    }
  
    @Override
    public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
        //读取Descriptor
    }
       
};

上述几个回调函数是BLE开发中不可缺少的;

6、等待设备连接成功

当调用targetdDevice.connectGatt(context, false, gattCallback)后系统会主动发起与BLE蓝牙设备的连接,若成功连接到设备将回调onConnectionStateChange方法,其处理过程如下:

@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,int newState) {
     if (newState == BluetoothGatt.STATE_CONNECTED) {
         Log.e(TAG, "设备连接上 开始扫描服务");
         // 连接成功后,开始扫描服务
         mBluetoothGatt.discoverServices();
     }
     if (newState == BluetoothGatt.STATE_DISCONNECTED) {
         // 连接断开
         /*连接断开后的相应处理*/      
     }
};

判断newState == BluetoothGatt.STATE_CONNECTED表明此时已经成功连接到设备;

7、开启扫描服务

mBluetoothGatt.discoverServices();

扫描BLE设备服务是安卓系统中关于BLE蓝牙开发的重要一步,一般在设备连接成功后调用,扫描到设备服务后回调onServicesDiscovered()函数,函数原型如下:

@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
    private List<BluetoothGattService> servicesList;
    //获取服务列表
    servicesList = mBluetoothGatt.getServices();
}
  • BLE蓝牙协议下数据的通信方式采用BluetoothGattService、BluetoothGattCharacteristic和BluetoothGattDescriptor三个主要的类实现通信;
  • BluetoothGattService 简称服务,是构成BLE设备协议栈的组成单位,一个蓝牙设备协议栈一般由一个或者多个BluetoothGattService组成;
  • BluetoothGattCharacteristic 简称特征,一个服务包含一个或者多个特征,特征作为数据的基本单元;
  • 一个BluetoothGattCharacteristic特征包含一个数据值和附加的关于特征的描述;
  • BluetoothGattDescriptor:用于描述特征的类,其同样包含一个value值;

8、获取负责通信的BluetoothGattCharacteristic

BLE蓝牙开发主要有负责通信的BluetoothGattService完成的。当且称为通信服务。通信服务通过硬件工程师提供的UUID获取。获取方式如下:

  • BluetoothGattService service = mBluetoothGatt.getService(UUID.fromString("蓝牙模块提供的负责通信UUID字符串"));
  • 通信服务中包含负责读写的BluetoothGattCharacteristic,且分别称为notifyCharacteristic和writeCharacteristic。其中notifyCharacteristic负责开启监听,也就是启动收数据的通道,writeCharacteristic负责写入数据;

具体操作方式如下:

  BluetoothGattService service = mBluetoothGatt.getService(UUID.fromString("蓝牙模块提供的负责通信服务UUID字符串"));
   // 例如形式如:49535343-fe7d-4ae5-8fa9-9fafd205e455
  notifyCharacteristic = service.getCharacteristic(UUID.fromString("notify uuid"));
  writeCharacteristic =  service.getCharacteristic(UUID.fromString("write uuid"));

9、开启监听

开启监听,即建立与设备的通信的首发数据通道,BLE开发中只有当客户端成功开启监听后才能与服务端收发数据。开启监听的方式如下:

mBluetoothGatt.setCharacteristicNotification(notifyCharacteristic, true)
BluetoothGattDescriptor descriptor = characteristic .getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
//若开启监听成功则会回调BluetoothGattCallback中的onDescriptorWrite()方法,处理方式如下:
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
   if (status == BluetoothGatt.GATT_SUCCESS) {
       //开启监听成功,可以向设备写入命令了
       Log.e(TAG, "开启监听成功");
   }
};

10、写入数据

BLE单次写的数据量大小是有限制的,通常是20字节,可以尝试通过requestMTU增大,但不保证能成功。分包写是一种解决方案,需要定义分包协议,假设每个包大小20字节,分两种包,数据包和非数据包。对于数据包,头两个字节表示包的序号,剩下的都填充数据。对于非数据包,主要是发送一些控制信息。
监听成功后通过向 writeCharacteristic写入数据实现与服务端的通信。写入方式如下:

//value为客户端向服务端发送的指令
writeCharacteristic.setValue(value);
mBluetoothGatt.writeCharacteristic(writeCharacteristic)

其中:value一般为Hex格式指令,其内容由设备通信的蓝牙通信协议规定;

11、接收数据

若写入指令成功则回调BluetoothGattCallback中的onCharacteristicWrite()方法,说明将数据已经发送给下位机;

@Override
public void onCharacteristicWrite(BluetoothGatt gatt,
    BluetoothGattCharacteristic characteristic, int status) {
    if (status == BluetoothGatt.GATT_SUCCESS) {
        Log.e(TAG, "发送成功");
    }
}

若发送的数据符合通信协议,则服务端会向客户端回复相应的数据。发送的数据通过回调onCharacteristicChanged()方法获取,其处理方式如下:

@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
    // value为设备发送的数据,根据数据协议进行解析
    byte[] value = characteristic.getValue();
}

通过向服务端发送指令获取服务端的回复数据,即可完成与设备的通信过程;

12、断开连接

当与设备完成通信之后之后一定要断开与设备的连接。调用以下方法断开与设备的连接:

mBluetoothGatt.disconnect();
mBluetoothGatt.close();

二、BLE服务端开发流程

1、设置广播以及初始化广播数据

//广播设置(必须)
AdvertiseSettings settings = new AdvertiseSettings.Builder()
        .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) //广播模式: 低功耗,平衡,低延迟
        .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) //发射功率级别: 极低,低,中,高
        .setTimeout(0)
        .setConnectable(true) //能否连接,广播分为可连接广播和不可连接广播
        .build();

//广播数据(必须,广播启动就会发送)
AdvertiseData advertiseData = new AdvertiseData.Builder()
        .setIncludeDeviceName(true) //包含蓝牙名称
        .setIncludeTxPowerLevel(true) //包含发射功率级别
        .addManufacturerData(1, new byte[]{23, 33}) //设备厂商数据,自定义
        .build();

//扫描响应数据(可选,当客户端扫描时才发送)
AdvertiseData scanResponse = new AdvertiseData.Builder()
        .addManufacturerData(2, new byte[]{66, 66}) //设备厂商数据,自定义
        .addServiceUuid(new ParcelUuid(UUID_SERVICE)) //服务UUID
//                .addServiceData(new ParcelUuid(UUID_SERVICE), new byte[]{2}) //服务数据,自定义
        .build();

2、开始广播

BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
//BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

// ============启动BLE蓝牙广播(广告) ===============
mBluetoothLeAdvertiser = bluetoothAdapter.getBluetoothLeAdvertiser();
mBluetoothLeAdvertiser.startAdvertising(settings, advertiseData, scanResponse, mAdvertiseCallback);

// BLE广播Callback
private AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {
    @Override
    public void onStartSuccess(AdvertiseSettings settingsInEffect) {
        logTv("BLE广播开启成功");
    }

    @Override
    public void onStartFailure(int errorCode) {
        logTv("BLE广播开启失败,错误码:" + errorCode);
    }
};

3、配置Services以及Characteristic

// 注意:必须要开启可连接的BLE广播,其它设备才能发现并连接BLE服务端!
// =============启动BLE蓝牙服务端======================================
BluetoothGattService service = new BluetoothGattService(UUID_SERVICE, BluetoothGattService.SERVICE_TYPE_PRIMARY);

//添加可读+通知characteristic
BluetoothGattCharacteristic characteristicRead = new BluetoothGattCharacteristic(UUID_CHAR_READ_NOTIFY,BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_NOTIFY, BluetoothGattCharacteristic.PERMISSION_READ);
characteristicRead.addDescriptor(new BluetoothGattDescriptor(UUID_DESC_NOTITY, BluetoothGattCharacteristic.PERMISSION_WRITE));
service.addCharacteristic(characteristicRead);

//添加可写characteristic
BluetoothGattCharacteristic characteristicWrite = new BluetoothGattCharacteristic(UUID_CHAR_WRITE, BluetoothGattCharacteristic.PROPERTY_WRITE, BluetoothGattCharacteristic.PERMISSION_WRITE);
service.addCharacteristic(characteristicWrite);

if (bluetoothManager != null){
    mBluetoothGattServer = bluetoothManager.openGattServer(this, mBluetoothGattServerCallback);
}

mBluetoothGattServer.addService(service);

4、Server回调以及操作

/**
* 服务事件的回调
*/
private BluetoothGattServerCallback mBluetoothGattServerCallback= new BluetoothGattServerCallback() {

   /**
    * 1.连接状态发生变化时
    */
   @Override
   public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
       Log.e(TAG, String.format("1.onConnectionStateChange:device name = %s, address = %s", device.getName(), device.getAddress()));
       Log.e(TAG, String.format("1.onConnectionStateChange:status = %s, newState =%s ", status, newState));
   }

   @Override
   public void onServiceAdded(int status, BluetoothGattService service) {
       Log.e(TAG, String.format("onServiceAdded:status = %s", status));
   }

   @Override
   public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {
       Log.e(TAG, String.format("onCharacteristicReadRequest:device name = %s, address = %s", device.getName(), device.getAddress()));
       Log.e(TAG, String.format("onCharacteristicReadRequest:requestId = %s, offset = %s", requestId, offset));

       mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, characteristic.getValue());
   }

   /**
    * 3. onCharacteristicWriteRequest,接收具体的字节
    */
   @Override
   public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] requestBytes) {
       Log.e(TAG, String.format("3.onCharacteristicWriteRequest:device name = %s, address = %s", device.getName(), device.getAddress()));
       Log.e(TAG, String.format("3.onCharacteristicWriteRequest:requestId = %s, preparedWrite=%s, responseNeeded=%s, offset=%s, value=%s", requestId, preparedWrite, responseNeeded, offset, requestBytes.toString()));
       mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, requestBytes);
       
       //4.处理响应内容
       onResponseToClient(requestBytes, device, requestId, characteristic);
   }

   /**
    * 2.描述被写入时,在这里执行 bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS...  收,触发 onCharacteristicWriteRequest
    */
   @Override
   public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
       Log.e(TAG, String.format("2.onDescriptorWriteRequest:device name = %s, address = %s", device.getName(), device.getAddress()));
       Log.e(TAG, String.format("2.onDescriptorWriteRequest:requestId = %s, preparedWrite = %s, responseNeeded = %s, offset = %s, value = %s,", requestId, preparedWrite, responseNeeded, offset, value.toString()));

       // now tell the connected device that this was all successfull
       mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);
   }

   /**
    * 5.特征被读取。当回复响应成功后,客户端会读取然后触发本方法
    */
   @Override
   public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {
       Log.e(TAG, String.format("onDescriptorReadRequest:device name = %s, address = %s", device.getName(), device.getAddress()));
       Log.e(TAG, String.format("onDescriptorReadRequest:requestId = %s", requestId));

       mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, null);
   }

   @Override
   public void onNotificationSent(BluetoothDevice device, int status) {
       super.onNotificationSent(device, status);
       Log.e(TAG, String.format("5.onNotificationSent:device name = %s, address = %s", device.getName(), device.getAddress()));
       Log.e(TAG, String.format("5.onNotificationSent:status = %s", status));
   }

   @Override
   public void onMtuChanged(BluetoothDevice device, int mtu) {
       super.onMtuChanged(device, mtu);
       Log.e(TAG, String.format("onMtuChanged:mtu = %s", mtu));
   }

   @Override
   public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
       super.onExecuteWrite(device, requestId, execute);
       Log.e(TAG, String.format("onExecuteWrite:requestId = %s", requestId));
   }
};

/**
* 4.处理响应内容
*
* @param reqeustBytes
* @param device
* @param requestId
* @param characteristic
*/
private void onResponseToClient(byte[] reqeustBytes, BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic) {
   Log.e(TAG, String.format("4.onResponseToClient:device name = %s, address = %s", device.getName(), device.getAddress()));
   Log.e(TAG, String.format("4.onResponseToClient:requestId = %s", requestId));
   Log.e(TAG, "4.收到:");

   String str = new String(reqeustBytes) + " hello>";
   characteristicRead.setValue(str.getBytes());
   mGattServer.notifyCharacteristicChanged(device, characteristicRead, false);

   Log.i(TAG, "4.响应:" + str);
   MainActivity.handler.obtainMessage(MainActivity.DEVICE, new     String(reqeustBytes)).sendToTarget();
}

三、源码下载

源码上传在CSDN上了,有需要的可以借鉴。

=====> Android蓝牙Ble通讯Demo示例源码–扫描,连接,发送和接收数据,分包解包

四、蓝牙操作的注意事项

1、如何避免ble蓝牙连接出现133错误?

  • Android 连接外围设备的数量有限,当不需要连接蓝牙设备的时候,必须调用 BluetoothGatt#close 方法释放资源;
  • 蓝牙 API 连接蓝牙设备的超时时间大概在 20s 左右,具体时间看系统实现。有时候某些设备进行蓝牙连接的时间会很长,大概十多秒。如果自己手动设置了连接超时时间在某些设备上可能会导致接下来几次的连接尝试都会在 BluetoothGattCallback#onConnectionStateChange 返回 state == 133;
  • 能否避免android设备与ble设备连接/断开时上报的133这类错误?
    1、在连接失败或者断开连接之后,调用 close 并刷新缓存
    2、尽量不要在startLeScan的时候尝试连接,先stopLeScan后再去连
    3、对同一设备断开后再次连接(连接失败重连),哪怕调用完close,需要等待一段时间(400毫秒试了1次,结果不 行;1000毫秒则再没出现过问题)后再去connectGatt
    4、可以在连接前都startLeScan一下,成功率要高一点

2、单次写的数据大小有20字节限制,如何发送长数据?

BLE单次写的数据量大小是有限制的,通常是20字节,可以尝试通过requestMTU增大,但不保证能成功。分包写是一种解决方案,需要定义分包协议,假设每个包大小20字节,分两种包,数据包和非数据包。对于数据包,头两个字节表示包的序号,剩下的都填充数据。对于非数据包,主要是发送一些控制信息。
总体流程如下:
1、定义通讯协议,如下(这里只是个举例,可以根据项目需求扩展)

消息号(1个字节)功能(1个字节)子功能(1个字节)数据长度(2个字节)数据内容(N个字节)CRC校验(1个字节)
0101010000--2D

2、封装通用发送数据接口(拆包)
该接口根据会发送数据内容按最大字节数拆分(一般20字节)放入队列,拆分完后,依次从队列里取出发送

3、封装通用接收数据接口(组包)
该接口根据从接收的数据按协议里的定义解析数据长度判读是否完整包,不是的话把每条消息累加起来

4、解析完整的数据包,进行业务逻辑处理

5、协议还可以引入加密解密,需要注意的选算法参数的时候,加密后的长度最好跟原数据长度一致,这样不会影响拆包组包

3、在Android不同版本或不同的手机扫描不到蓝牙设备

一般都是Android版本适配以及不同ROM机型(小米/红米、华为/荣耀等)(EMUI、MIUI、ColorOS等)的权限问题

4、其它

  • 蓝牙的写入操作, 读取操作必须序列化进行. 写入数据和读取数据是不能同时进行的, 如果调用了写入数据的方法, 马上调用又调用写入数据或者读取数据的方法,第二次调用的方法会立即返回 false, 代表当前无法进行操作;

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

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

相关文章

【OLED驱动】:stm32F103的四线模拟IIC驱动的0.96寸OLED屏幕显示数字,字母,汉字,图片的例程

文章目录 前言 一、四线OLED是什么&#xff1f; 二、IIC原理 1.四线0.96寸OLED是IIC驱动来实现显示 2.IIC协议的OLED屏幕 3.STM32驱动代码 4.IIC使用代码 总结 前言 最近在做一个开源方案 — 平衡车&#xff0c;即将开源硬件和软件&#xff0c;会在CSDN上开源&#xff0…

Vue2+Vue3基础入门到实战项目(五)——课程学习笔记

大家好&#xff0c; Capybara 继续与大家一起学习Vue框架。书读百遍其义自见。 day06 路由进阶 路由模块封装 router/index.js 所抽离内容包括&#xff1a;导入组件、&#xff08;额外需要&#xff09;导入Vue、导入VueRouter插件、 创建路由对象、导出路由对象 需要注意路径…

上线检查工具(待完善)

根据V11《CEBPM系统上线CheckList》整理而得&#xff0c;适用于V11&#xff0c;DHERP&#xff0c;Oracle和MSSQL数据库&#xff0c;检查内容还不完善。 上图&#xff1a; 1&#xff09;数据库连接 2&#xff09;双击[连接别名]&#xff0c;可选择历史连接 3&#xff09;主界面…

若依 MyBatis改为MyBatis-Plus

主要内容&#xff1a;升级成mybatis-plus&#xff0c;代码生成也是mybatis-plus版本 跟着我一步一步来&#xff0c;就可完成升级&#xff01; 检查&#xff1a;启动程序&#xff0c;先保证若依能启动 第一步&#xff1a;添加依赖 这里需要在两个地方添加&#xff0c;一个是最…

CSRF攻击原理及防护

CSRF攻击原理及防护 0x01 CSRF是什么 ssrf 服务端请求伪造 CSRF全称为跨站请求伪造&#xff08;Cross-site request forgery&#xff09;&#xff0c;是一种网络攻击方式&#xff0c;也被称为 one-click attack 或者 session riding。 0x02 CSRF攻击原理 CSRF攻击利用网站…

flutter架构全面解析

Flutter 是一个跨平台的 UI 工具集&#xff0c;它的设计初衷&#xff0c;就是允许在各种操作系统上复用同样的代码&#xff0c;例如 iOS 和 Android&#xff0c;同时让应用程序可以直接与底层平台服务进行交互。如此设计是为了让开发者能够在不同的平台上&#xff0c;都能交付拥…

智慧公厕三大数字化远景,畅通数字化循环、夯实数字化基建、开创数字化创新

智慧公厕&#xff1a;实现数字化远景&#xff0c;提升公厕使用、建设与管理的创新之路 在现代社会中&#xff0c;公厕作为城市基础设施的重要组成部分&#xff0c;对城市形象和居民生活质量有着重要影响。然而&#xff0c;一些传统公厕管理的繁琐和配置设施的陈旧&#xff0c;…

centos7升级openssh版本

linux升级openssh版本&#xff0c;升级到8.6p1 小白教程&#xff0c;一看就会&#xff0c;一做就成。 1.下载rpm包 2.编写一键安装脚本&#xff08;然后执行&#xff09; #把所有的rpm包&#xff0c;我都放到了/ydy目录&#xff0c;下面安装时&#xff0c;也指定了这个目录 #编…

Golang-GJSON 快速而简单的方法来从 json 文档获取值

GJSON 是一个 Go 包&#xff0c;它提供了一种快速而简单的方法来从 json 文档获取值。它具有单行搜索、点符号路径、迭代和解析 json 行等功能。 GJSON 也可用于Python和Rust 入门 安装中 要开始使用GJSON 请安装 Go 并运行 go get &#xff1a; $ go get -u github.com/ti…

HarmonyOS开发:走进静态共享包的依赖与使用

前言 在上一篇&#xff0c;我们进行了动态共享包的开发和使用&#xff0c;由于动态共享包有一定的局限性&#xff0c;比如&#xff0c;调用共享包资源还得要通过工具类进行调用&#xff0c;再比如仅用于应用内部代码、资源的共享&#xff0c;如果我想要开源&#xff0c;以远程依…

STM32纯中断方式发送接收数据(串行通信;keil arm5;)

除了main文件其他文件均无修改&#xff0c;正常运行--在keil arm5内

【腾讯云Cloud Studio实战训练营】戏说cloud studio

文章目录 前言产品概述项目体验登录空间模板模板项目体验 总结 前言 在奇幻世界中&#xff0c;存在着一片神秘的云海&#xff0c;被人们称为腾讯云云端开发环境 Cloud Studio。这片云海是一座巨大的浮岛&#xff0c;上面漂浮着一个集成式开发环境&#xff08;Integrated Devel…

2023年行研行业研究报告

第一章 行业概述 1.1 行研行业 行业定义为同一类别的经济活动&#xff0c;这涉及生产相似产品、应用相同生产工艺或提供同类服务的集合&#xff0c;如食品饮料行业、服饰行业、机械制造行业、金融服务行业和移动互联网行业等。 为满足全球金融业的需求&#xff0c;1999年8月…

指针练习(2)

1. int main() {int a[5][5];int(*p)[4];p a;printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);return 0; } 解析&#xff1a;a是一个5行5列的二维数组&#xff0c;而p是一个访问4个元素的数组指针&#xff0c;将数组a的首元素地址也…

【开发问题系列】CSV转Excel

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…

innovus: 加IO buffer

我正在「拾陆楼」和朋友们讨论有趣的话题&#xff0c;你⼀起来吧&#xff1f; 拾陆楼知识星球入口 set fid [open $net_file w] foreach port_name $input_ports { set net [get_nets -q -of_object [get_ports $port_name]] set net_name [get_attribute $net full_name] …

python实现对excel表中的某列数据进行排序

如下需要对webCms中的B列数据进行升序排序&#xff0c;且不能影响到其他列、工作表中的数据和格式。 import pandas as pd import openpyxl from openpyxl.utils.dataframe import dataframe_to_rows# 读取 Excel 文件 file_path 1.xlsx sheet_name webCms# 读取 Excel 文件并…

Java之文件操作与IO

目录 一.认识文件 1.1文件是什么&#xff1f; 1.2文件的组织 1.3文件路径 1.4文件的分类 二.文件操作 2.1File概述 三.文件内容操作--IO 3.1JavaIO的认识 3.2Reader和Writer ⭐Reader类 ⭐Writer类 3.2FileInputStream和FileOutputStream ⭐FileInputStream类 …

scala基础

scala 基础 1. scala简介 scala是运行在 JVM 上的多范式编程语言&#xff0c;同时支持面向对象和面向函数编程早期scala刚出现的时候&#xff0c;并没有怎么引起重视&#xff0c;随着Spark和Kafka这样基于scala的大数据框架的兴起&#xff0c;scala逐步进入大数据开发者的眼帘…

线程同步机制类封装及线程池实现

1.线程池 线程池是由服务器预先创建的一组子线程&#xff0c;线程池中的线程数量应该和 CPU 数量差不多。线程池中的所有子线程都运行着相同的代码。当有新的任务到来时&#xff0c;主线程将通过某种方式选择线程池中的某一个子线程来为之服务。相比与动态的创建子线程&#x…