Bootstrap

(Java和kotlin)Android Blufi低功耗BLE蓝牙协议解析封装工具类

BluFi 是一项基于蓝牙通道的 Wi-Fi 网络配置功能,适用于 ESP32。它通过安全协议将 Wi-Fi 的 SSID、密码等配置信息传输到 ESP32。基于这些信息,ESP32 可进而连接到 AP 或建立 SoftAP。 BluFi 流程的关键部分包括数据的分片、加密以及校验和验证。 用户可按需自定义用于对称加密、非对称加密以及校验的算法。此处,我们采用 DH 算法进行密钥协商,128-AES 算法用于数据加密,CRC16 算法用于校验和验证。

BLE通信中,协议的理解和封装是非常重要的。通常,BLE设备会通过GATT协议定义服务特征值(Characteristics),而具体的通信协议则由设备厂商定义。我们可以通过封装一个协议解析和生成的工具类来简化BLE通信的开发。
在这里插入图片描述
在这里插入图片描述

以下是一个示例,展示如何封装一个BLE协议解析生成的工具类。

BluFiFrame封装类(Java版)

import java.util.Arrays;

public class BluFiFrame {
    private static final int HEADER_LENGTH = 4; // 类型 + 帧控制 + 序列号 + 数据长度
    private static final int CHECKSUM_LENGTH = 2;

    public byte type;
    public byte frameControl;
    public byte sequenceNumber;
    public byte[] data;
    public boolean isFragmented;

    /**
     * 生成BluFi帧
     */
    public byte[] buildFrame() {
        int frameLength = HEADER_LENGTH + data.length + CHECKSUM_LENGTH + (isFragmented ? 2 : 0);
        byte[] frame = new byte[frameLength];

        // 写入头部
        frame[0] = type;
        frame[1] = frameControl;
        frame[2] = sequenceNumber;
        frame[3] = (byte) data.length;

        int offset = HEADER_LENGTH;

        // 如果是分片帧,写入2字节的内容总长度
        if (isFragmented) {
            frame[offset++] = (byte) (data.length >> 8);
            frame[offset++] = (byte) (data.length & 0xFF);
        }

        // 写入数据
        System.arraycopy(data, 0, frame, offset, data.length);
        offset += data.length;

        // 计算并写入校验和
        byte[] checksum = calculateChecksum(frame, 0, offset);
        frame[offset++] = checksum[0];
        frame[offset] = checksum[1];

        return frame;
    }

    /**
     * 解析BluFi帧
     */
    public static BluFiFrame parseFrame(byte[] frame) throws IllegalArgumentException {
        if (frame.length < HEADER_LENGTH + CHECKSUM_LENGTH) {
            throw new IllegalArgumentException("Frame too short");
        }

        BluFiFrame bluFiFrame = new BluFiFrame();
        bluFiFrame.type = frame[0];
        bluFiFrame.frameControl = frame[1];
        bluFiFrame.sequenceNumber = frame[2];
        byte dataLength = frame[3];

        bluFiFrame.isFragmented = (bluFiFrame.frameControl & 0x01) != 0; // 检查分片位
        int dataStartIndex = HEADER_LENGTH + (bluFiFrame.isFragmented ? 2 : 0); // 分片帧多2字节的内容总长度

        // 检查数据长度是否有效
        if (frame.length < dataStartIndex + dataLength + CHECKSUM_LENGTH) {
            throw new IllegalArgumentException("Invalid data length");
        }

        // 提取数据
        bluFiFrame.data = Arrays.copyOfRange(frame, dataStartIndex, dataStartIndex + dataLength);

        // 校验和验证
        byte[] checksum = Arrays.copyOfRange(frame, frame.length - CHECKSUM_LENGTH, frame.length);
        byte[] expectedChecksum = calculateChecksum(frame, 0, frame.length - CHECKSUM_LENGTH);

        if (!Arrays.equals(checksum, expectedChecksum)) {
            throw new IllegalArgumentException("Checksum mismatch");
        }

        return bluFiFrame;
    }

    /**
     * 计算校验和(2字节)
     */
    private static byte[] calculateChecksum(byte[] data, int offset, int length) {
        int sum = 0;
        for (int i = offset; i < length; i++) {
            sum += data[i] & 0xFF;
        }
        return new byte[]{(byte) (sum >> 8), (byte) (sum & 0xFF)};
    }

    @Override
    public String toString() {
        return String.format("BluFiFrame{type=0x%02X, frameControl=0x%02X, sequenceNumber=0x%02X, data=%s, isFragmented=%b}",
                type, frameControl, sequenceNumber, Arrays.toString(data), isFragmented);
    }
}

使用示例
生成帧

BluFiFrame frame = new BluFiFrame();
frame.type = 0x01; // 帧类型
frame.frameControl = 0x00; // 不分片
frame.sequenceNumber = 0x01; // 序列号
frame.data = new byte[]{0x10, 0x20, 0x30}; // 数据
frame.isFragmented = false; // 不分片

byte[] packet = frame.buildFrame();
System.out.println("Generated frame: " + Arrays.toString(packet));

解析帧

byte[] receivedFrame = new byte[]{
        0x01, 0x00, 0x01, 0x03, // 类型、帧控制、序列号、数据长度
        0x10, 0x20, 0x30,       // 数据
        0x00, 0x63              // 校验和
};

try {
    BluFiFrame parsedFrame = BluFiFrame.parseFrame(receivedFrame);
    System.out.println("Parsed frame: " + parsedFrame);
} catch (IllegalArgumentException e) {
    System.err.println("Failed to parse frame: " + e.getMessage());
}

输出示例
生成帧

Generated frame: [1, 0, 1, 3, 16, 32, 48, 0, 99]

解析帧

Parsed frame: BluFiFrame{type=0x01, frameControl=0x00, sequenceNumber=0x01, data=[16, 32, 48], isFragmented=false}

///

Kotlin 实现
BluFiFrame封装类(Kotlin版)

class BluFiFrame(
    var type: Byte,
    var frameControl: Byte,
    var sequenceNumber: Byte,
    var data: ByteArray,
    var isFragmented: Boolean
) {
    companion object {
        private const val HEADER_LENGTH = 4 // 类型 + 帧控制 + 序列号 + 数据长度
        private const val CHECKSUM_LENGTH = 2

        /**
         * 解析 BluFi 帧
         */
        fun parseFrame(frame: ByteArray): BluFiFrame {
            require(frame.size >= HEADER_LENGTH + CHECKSUM_LENGTH) { "Frame too short" }

            val type = frame[0]
            val frameControl = frame[1]
            val sequenceNumber = frame[2]
            val dataLength = frame[3].toInt()
            val isFragmented = (frameControl.toInt() and 0x01) != 0 // 检查分片位

            val dataStartIndex = HEADER_LENGTH + if (isFragmented) 2 else 0 // 分片帧多2字节的内容总长度
            require(frame.size >= dataStartIndex + dataLength + CHECKSUM_LENGTH) { "Invalid data length" }

            // 提取数据
            val data = frame.copyOfRange(dataStartIndex, dataStartIndex + dataLength)

            // 校验和验证
            val checksum = frame.copyOfRange(frame.size - CHECKSUM_LENGTH, frame.size)
            val expectedChecksum = calculateChecksum(frame, 0, frame.size - CHECKSUM_LENGTH)
            require(checksum.contentEquals(expectedChecksum)) { "Checksum mismatch" }

            return BluFiFrame(type, frameControl, sequenceNumber, data, isFragmented)
        }

        /**
         * 计算校验和(2字节)
         */
        private fun calculateChecksum(data: ByteArray, offset: Int, length: Int): ByteArray {
            var sum = 0
            for (i in offset until length) {
                sum += data[i].toInt() and 0xFF
            }
            return byteArrayOf((sum shr 8).toByte(), (sum and 0xFF).toByte())
        }
    }

    /**
     * 生成 BluFi 帧
     */
    fun buildFrame(): ByteArray {
        val frameLength = HEADER_LENGTH + data.size + CHECKSUM_LENGTH + if (isFragmented) 2 else 0
        val frame = ByteArray(frameLength)

        // 写入头部
        frame[0] = type
        frame[1] = frameControl
        frame[2] = sequenceNumber
        frame[3] = data.size.toByte()

        var offset = HEADER_LENGTH

        // 如果是分片帧,写入2字节的内容总长度
        if (isFragmented) {
            frame[offset++] = (data.size shr 8).toByte()
            frame[offset++] = (data.size and 0xFF).toByte()
        }

        // 写入数据
        System.arraycopy(data, 0, frame, offset, data.size)
        offset += data.size

        // 计算并写入校验和
        val checksum = calculateChecksum(frame, 0, offset)
        frame[offset++] = checksum[0]
        frame[offset] = checksum[1]

        return frame
    }

    override fun toString(): String {
        return "BluFiFrame(type=0x${type.toHex()}, frameControl=0x${frameControl.toHex()}, " +
               "sequenceNumber=0x${sequenceNumber.toHex()}, data=${data.toHexString()}, " +
               "isFragmented=$isFragmented)"
    }

    // 扩展函数:将 Byte 转换为十六进制字符串
    private fun Byte.toHex(): String = "%02X".format(this)

    // 扩展函数:将 ByteArray 转换为十六进制字符串
    private fun ByteArray.toHexString(): String = joinToString(" ") { "%02X".format(it) }
}

优化点
使用 Kotlin 的数据类和扩展函数

简化了代码结构,提高了可读性。

使用扩展函数将 Byte 和 ByteArray 转换为十六进制字符串,方便调试。

更简洁的错误处理

使用 require 函数进行参数校验,代码更简洁。

使用示例
生成帧

val frame = BluFiFrame(
    type = 0x01,
    frameControl = 0x00,
    sequenceNumber = 0x01,
    data = byteArrayOf(0x10, 0x20, 0x30),
    isFragmented = false
)

val packet = frame.buildFrame()
println("Generated frame: ${packet.toHexString()}")

解析帧

val receivedFrame = byteArrayOf(
    0x01, 0x00, 0x01, 0x03, // 类型、帧控制、序列号、数据长度
    0x10, 0x20, 0x30,       // 数据
    0x00, 0x63              // 校验和
)

try {
    val parsedFrame = BluFiFrame.parseFrame(receivedFrame)
    println("Parsed frame: $parsedFrame")
} catch (e: IllegalArgumentException) {
    println("Failed to parse frame: ${e.message}")
}

输出示例
生成帧

Generated frame: 01 00 01 03 10 20 30 00 63

解析帧

Parsed frame: BluFiFrame(type=0x01, frameControl=0x00, sequenceNumber=0x01, data=10 20 30, isFragmented=false)

总结
Kotlin 版本的 BluFiFrame 更加简洁和易读,同时保留了核心功能。

;