【uniapp】BLE及通信过程+举例EPS32-arduino 与uniapp间蓝牙JSON通信

BLE链接与通信过程

外围设备,即服务器,初始化启动蓝牙外设,不断发出广播信号。当与中心设备,即客户端连接。

中心设备,即客户端,初始化启动蓝牙外设,进行扫描得到周围的外围设备MAC地址。停止扫描,指定mac地址与特定的外围设备连接。

连接建立后,服务器可以向客户端发Service及其Characteristic。客户端可以接收特定UUID的Service及其Characteristic。

客户端可以发送特定UUID的Service及其Characteristic给服务器。

BLE及通信过程

BLE全称为Bluetooth Low Energy,低功耗蓝牙技术,它比起传统蓝牙更省电,能够在安卓设备之间进行短距离通信。BLE只有在需要时才会连接,这样有效地减少了能耗。虽然BLE传输速度较低,但它能够实现实时数据传输和双向通信,非常适合传输小量数据。此外,BLE还可以与多种设备相连,而且还可以使用广播模式来发送信息,如心率检测仪,健身设备等。

蓝牙BLE有两个角色,分别是中心设备和外围设备

  • 外围设备:指功耗更低的设备,会不断地发出广播,直到与中心设备连接

  • 中心设备:可以进行扫描,寻找外设广播,并从广播中拿到数据

整个连接流程来说,就是外围设备要先添加服务,然后发送广播,中心设备开始进行扫描,扫描到后进行连接,然后数据进行交互。

GATT协议中定义了两个角色,一个是Service,一个是Characteristic,每个Service可以包含多个Characteristic,且他们都有特定的UUID,类似于 0000ff00-0000-1000-8000-00805f9b34fb 这样的字符串。每个Service代表提供某种服务的能力,比如跟心率有关的Service;Characteristic代表的是一个键值对,Service就是通过这个一个个的键值对达到传输数据的目的。

当主设备和从设备连接上GATT协议之后,就可以询问从设备可以提供哪些服务,在得到从设备的反馈报文后,就可以通过双方协商好的UUID获取到Service服务,然后再从Service中根据UUID获取到可读的Characteristic和可写的Characteristic,操作Characteristic就可以实现具体的数据通信。

UUID

UUID 即为“Universally Unique Identifier”用于标识蓝牙服务以及通讯特征访问属性,不同的蓝牙服务和属性使用不同的访问方法。

蓝牙技术联盟SIG定义UUID共用了一个基本的UUID:

0x0000xxxx-0000-1000-8000-00805F9B34FB。总共128位

为了进一步简化基本UUID,每一个蓝牙技术联盟定义的属性有一个唯一的16位UUID,以代替上面的基本UUID的‘x’部分。使用16位的UUID便于记忆和操作,如SIG定义了“Device Information”的16位UUID为0x180A。

所以我们自己在蓝牙通讯时,可以自行定义上面xxxx部分,用于识别自己的蓝牙方式,并且在扫描蓝牙时可能通过定义好的UUID来过滤掉没有必要的蓝牙广播。

特征及描述符

特征 Characteristic

Characteristic 是Gatt通信最小的逻辑单元,一个 characteristic 包含一个单一 value 变量 和 0-n个用来描述 characteristic 变量的描述符 Descriptor。与 service 相似,每个 characteristic 用 16bit或者32bit的uuid作为标识,实际的通信中,也是通过 Characteristic 进行读写通信的。

描述符 Descriptor

它的定义就是描述 GattCharacteristic 值已定义的属性,比如指定可读的属性,可接受范围等,比如为写的 特征添加描述符。

UUID与蓝牙介绍

UUID全称:Universally Unique Identifier,即通用唯一识别码。 由一组32位数的16进制数字所构成,总数为16^32 = 2^128=3.4 x 10^38。 UUID的标准型式包含32个16进制数字; 以连字号分为五段,形式为8-4-4-4-12的32个字符。 如:550e8400-e29b-41d4-a716-446655440000

UUID的作用:让分布式系统中的所有元素都能有唯一的辨识信息,而不需要通过中央控制端来做辨识信息的指定。这样就能建立不与其他人冲突的UUID。

在蓝牙领域技术中的对于UUID的概念,需要先讲述一下蓝牙服务是什么? 蓝牙服务组成:使用蓝牙对外提供服务的设备,需要有对应的服务功能。比如使用蓝牙听歌时,需要提供音频播放的功能,这种具体的功能,就是蓝牙服务。这些服务分为服务、特性、属性三个部分。 为了明确标准的蓝牙服务,蓝牙技术联盟SIG定义UUID共用了一个基本的UUID:0x0000xxxx-0000-1000-8000-00805F9B34FB,总共128位。为了进一步简化基本UUID,每一个蓝牙技术联盟定义的属性有一个唯一的16位UUID,以代替上面的基本UUID的‘x’部分。 在服务过程中,需要使用UUID用于标识蓝牙服务以及通讯特征访问属性,不同的蓝牙服务和属性使用不同的访问方法。只有找到正确的UUID,才能使用正确的功能。在“GATT层”中定义的所有属性都有一个UUID值,用来识别不同的特性。

83fe5890-c125-45e7-9b63-7b42c7825404

蓝牙广播中对服务UUID格式定义都有三种 16 bit UUID32 bit UUID128 bit UUID 若 16 bit UUID为xxxx,那么 128 bit UUID 为 0000xxxx-0000-1000-8000-00805F9B34FB 若 32 bit UUID为xxxxxxxx,那么 128 bit UUID 为xxxxxxxx-0000-1000-8000-00805F9B34FB

网址:https://www.uuidgenerator.net/可以产生UUID。

一、 服务(Service) UUID 服务(Service)可以理解为组长,一个组里面至少有一个或多个特性(Characteristic),特性(Characteristic)可以理解为组员。不同的服务(Service)应该有不同的编号(UUID),用以区分不同的服务(Service)。

二、特性(Characteristic)UUID 特性(Characteristic)是依附于某个服务(Service)的,可以理解为组员,每个组员至少要有一个编号(UUID)以及一个或多个属性(Property),每个特性(Characteristic)可以同时有一个或多个属性

三、属性(Property)UUID 常用的属性有如下几个,我们以手机和蓝牙模块进行通讯来说明: Read: 读属性,具有该属性的UUID 是可读的,也就是说这个属性允许手机来读取一些信息。手机可以发送这个指令来读取某个具有读属性UUID的信息。

Notify: 通知属性,具有该属性的UUID是可以发送通知的,也就是说具有这个属性的特性(Characteristic)可以主动发送信息给手机。 Write: 写属性,具有该属性的 UUID 是可以接收写入数据的。通常手机发送数据给蓝牙模块就是通过这个属性完成的。这个属性在Write 完成后,会发送写入完成结果给手机,然后手机再可以写入下一包,这个属性在写入一包数据后,需要等待应用层返回写入结果,速度比较慢。 Write Without Response:写属性,从字面意思上看,只是写,不需要返回写的结果,这个属性的特点是不需要应用层返回,完全依靠协议层完成,速度快,但是写入速度超过协议处理速度的时候,会丢包。 扛把子(服务)是0xFFE0, 小弟只有一个(特性)是0xFFE1,小弟(特性)0xFFE1同时具有读、通知、不需要返回结果的写属性。

 

ESP32作为服务器

 

复制代码
/*
    Video: https://www.youtube.com/watch?v=oCMOYS71NIU
    Based on Neil Kolban example for IDF: https://github.com/nkolban/esp32-snippets/blob/master/cpp_utils/tests/BLE%20Tests/SampleNotify.cpp
    Ported to Arduino ESP32 by Evandro Copercini

   Create a BLE server that, once we receive a connection, will send periodic notifications.
   The service advertises itself as: 6E400001-B5A3-F393-E0A9-E50E24DCCA9E
   Has a characteristic of: 6E400002-B5A3-F393-E0A9-E50E24DCCA9E - used for receiving data with "WRITE"
   Has a characteristic of: 6E400003-B5A3-F393-E0A9-E50E24DCCA9E - used to send data with  "NOTIFY"

   The design of creating the BLE server is:
   1. Create a BLE Server
   2. Create a BLE Service
   3. Create a BLE Characteristic on the Service
   4. Create a BLE Descriptor on the characteristic
   5. Start the service.
   6. Start advertising.

   In this example rxValue is the data received (only accessible inside that function).
   And txValue is the data to be sent, in this example just a byte incremented every second.
*/
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include "String.h"
#include <ArduinoJson.h>  // 需安装 ArduinoJson库

BLEServer *pServer = NULL;
BLECharacteristic *pTxCharacteristic;
bool deviceConnected = false;
bool oldDeviceConnected = false;
uint8_t txValue = 0;
String Receive_Char_Read = "";
String Receive_char;
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"  // UART service UUID
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
StaticJsonDocument<200> jsonBuffer;  //声明一个JsonDocument对象,长度200
StaticJsonDocument<200> doc;
void str2json(String jsonString) {
  
  // 反序列化JSON
  DeserializationError error = deserializeJson(jsonBuffer, jsonString);
  if (error) {
    Serial.print(F("deserializeJson() failed: "));
    Serial.println(error.f_str());
    return;
  }
  // 解析JSON
  const char *str = jsonBuffer["name"];  // 读取字符串
  int data1 = jsonBuffer["age"];     // 读取整形数据
  Serial.println(str);
  Serial.println(data1);
  return;
};

class MyServerCallbacks : public BLEServerCallbacks {
  void onConnect(BLEServer *pServer) {
    deviceConnected = true;
  };

  void onDisconnect(BLEServer *pServer) {
    deviceConnected = false;
  }
};

class MyCallbacks : public BLECharacteristicCallbacks {

  void onWrite(BLECharacteristic *pCharacteristic) {
    String rxValue = pCharacteristic->getValue();

    if (rxValue.length() > 0) {
      Serial.println("*********");
      Serial.print("Received Value: ");
      for (int i = 0; i < rxValue.length(); i++) {
        Serial.print(rxValue[i]);
      }
      str2json(rxValue);
      Serial.println();
      Serial.println("*********");
    }
  }
};

void setup() {
  Serial.begin(115200);

  // Create the BLE Device 初始化蓝牙外设
  BLEDevice::init("UART Service");

  // Create the BLE Server 创建一个服务器
  pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());

  // Create the BLE Service 创建一个BLE服务
  BLEService *pService = pServer->createService(SERVICE_UUID);

  // Create a BLE Characteristic 创建一个BLE特性 通知属性
  pTxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY);

  pTxCharacteristic->addDescriptor(new BLE2902());  // 绑定描述符
  // Create a BLE Characteristic 创建一个BLE特性  读属性
  BLECharacteristic *pRxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE);

  pRxCharacteristic->setCallbacks(new MyCallbacks());  // 绑定回调函数

  // Start the service 启动服务器
  pService->start();

  // Start advertising  开始广播
  pServer->getAdvertising()->start();
  Serial.println("Waiting a client connection to notify...");
}

void loop() {

  if (deviceConnected) {
    // json数据及其字符串化
    
    doc["sensor"] = "sensor";
    doc["value"] = 1234;
    String jsonString;
    serializeJson(doc, jsonString);
    // 发送Json字符串
    pTxCharacteristic->setValue(jsonString);  // 设定特性值
    //Serial.println(jsonString);
    //pTxCharacteristic->setValue(txString);
    pTxCharacteristic->notify();  //发送特性(描述符、值等)
    txValue++;
    delay(100);  // bluetooth stack will go into congestion, if too many packets are sent
  }

  // disconnecting
  if (!deviceConnected && oldDeviceConnected) {
    delay(500);                   // give the bluetooth stack the chance to get things ready
    pServer->startAdvertising();  // restart advertising
    Serial.println("start advertising");
    oldDeviceConnected = deviceConnected;
  }
  // connecting
  if (deviceConnected && !oldDeviceConnected) {
    // do stuff here on connecting
    oldDeviceConnected = deviceConnected;
  }
}
复制代码

UNIAPP作为客户端    

复制代码
<template>
    <view class="content">
        <view>
            <button @click="scan" class="scan-btn">刷新</button>
            <button @click="getServices">获取蓝牙服务</button>
            <button @click="getCharacteristics">获取特征值</button>
            <button @click="notify">开启消息监听</button>
            <button @click="send">发送数据</button>
        </view>

        <view style="color:green;padding: 22px 8px;font-size: 22px;">接收到数据:{{received}}</view>

        <view class="dev-list">
            <h4>设备列表({{devices.length}}台):</h4>
            <div v-for="item in devices" class="deviceItem" :key="item.deviceId">
                <span>名称:{{item.name}}</span>
                <span>id:{{item.deviceId}}</span>

                <div style="text-align: right;width: 100%;font-size: 20px;">
                    <a @click="connectClick(item)" v-if="connectId !== item.deviceId">连接</a>
                    <a @click="disConnect(item)" v-else style="color: red;">断开连接</a>
                </div>
            </div>
        </view>
    </view>
</template>

<script>
    // 文档地址 https://uniapp.dcloud.net.cn/api/system/ble.html
    // 同ESP32内定义的UUID
    const SERVICE_UUID = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"; // UART service UUID
    const CHARACTERISTIC_UUID_RX = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E";
    const CHARACTERISTIC_UUID_TX = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E";
    export default {
        data() {
            return {
                // 是否已连接
                connected: false,
                inited: false,
                stopScan: false,

                // 当前连接的设备id
                connectId: "",
                devices: [], //蓝牙周围设备列表

                // 接收到的数据
                received: "",

            }
        },
        mounted() {
            //console.log(this.$myGlobalVar); // 输出 "Hello, world!"  
        },
        onLoad() {

        },
        onUnload() {

        },
        methods: {
            /*-----------------------------------------------------------------------*/
            // 开启蓝牙外设
            initBlue() {
                console.log('初始化蓝牙开始...')
                const _this = this;
                return new Promise((resolve) => {
                    uni.openBluetoothAdapter({
                        success(res) {
                            console.log('初始化蓝牙成功')
                            _this.inited = true;
                            // 扫描蓝牙设备
                            _this.scan();
                            resolve();
                        },
                        fail(err) {
                            console.log('初始化蓝牙失败', err)
                            uni.showModal({
                                title: '失败提示',
                                content: "初始化蓝牙失败",
                                showCancel: false
                            });
                        }
                    })
                })
            },
            /*-----------------------------------------------------------------------*/
            // 扫描外围设备,包括ESP32
            async scan() {
                const _this = this;
                if (!_this.inited) {
                    await _this.initBlue(); //注意this的作用域
                }
                // 扫描外围设备
                uni.startBluetoothDevicesDiscovery({
                    success(res) {
                        this.stopScan = false;
                        console.log('启动搜索')
                    },
                    fail(err) {
                        console.log('启动搜索失败', err)
                        uni.showModal({
                            title: '失败提示',
                            content: "启动搜索失败",
                            showCancel: false
                        });
                    }
                })

                // 扫描到外围蓝牙设备后的回调
                uni.onBluetoothDeviceFound(({
                    devices
                }) => {
                    _this.devices.push(...devices);
                    // 去重
                    _this.devices = [..._this.devices].reduce((pre, cur) => {
                        if ((pre.find(item => item.deviceId == cur.deviceId)) == null) {
                            pre.push(cur)
                        }
                        return pre;
                    }, [])

                })
            },
            /*-----------------------------------------------------------------------*/

            // 字符串转为ArrayBuffer对象,参数为字符串
            str2ab(str) {
                var buf = new ArrayBuffer(str.length * 2); // 每个字符占用2个字节
                var bufView = new Uint16Array(buf);
                for (var i = 0, strLen = str.length; i < strLen; i++) {
                    bufView[i] = str.charCodeAt(i);
                }
                return buf;
            },
            /*-----------------------------------------------------------------------*/
            // 点击某个设备的连接
            async connectClick(item) {
                console.log('即将连接蓝牙:', item);
                // 如果当前有连接的设备,需要先断开
                if (this.connected) {
                    await this.disConnect();
                }
                if (!this.inited) {
                    await this.initBlue();
                }
                // 停止扫描设备, 否则会浪费性能
                //uni.stopBluetoothDevicesDiscovery({});
                await uni.stopBluetoothDevicesDiscovery({
                    success(res) {
                        console.log("停止蓝牙设备搜索")
                        //console.log(res)
                        this.stopScan = true;
                    },
                    fail(err) {
                        console.error(err);
                        this.stopScan = false;
                    }
                });
                // 连接指定蓝牙设备
                //console.log('开始连接蓝牙设备');
                this.connectId = item.deviceId;
                this.connect();
            },
            /*-----------------------------------------------------------------------*/
            setMTU() {
                const _this = this;
                //3)设置MTU,否则传输报文不全,默认是23bytes,但是蓝牙本身是需要3bytes,故而只能传输20bytes
                setTimeout(() => {
                    console.log('deviceId>>>', _this.connectId);
                    uni.setBLEMTU({
                        deviceId: _this.connectId,
                        mtu: 255,
                        success: (res) => {
                            console.log('设置MTU最大值255成功', res);
                        },
                        fail: (res) => {
                            console.log('设置MTU最大值255失败', res);
                        }
                    });
                }, 1000);

            },
            connect() {
                const _this = this;

                console.log('设备id', _this.connectId);
                const promise = new Promise((resolve) => {
                    uni.showLoading({
                        title: '连接蓝牙设备中...'
                    })
                    uni.createBLEConnection({
                        deviceId: _this.connectId,
                        success(res) {
                            console.log('蓝牙设备连接成功');
                            console.log(res);
                            // 设置MTU,传输报文255字节
                            _this.setMTU();
                            // 在需要关闭加载提示的时候,调用 uni.hideLoading 方法
                            uni.hideLoading();
                        },
                        fail(err) {
                            console.log('蓝牙设备连接失败');
                            console.error(err);
                            // 在需要关闭加载提示的时候,调用 uni.hideLoading 方法
                            uni.hideLoading();
                        }
                    });
                });

            },
            /*-----------------------------------------------------------------------*/
            disConnect() {
                const _this = this;
                if (!_this.connectId) return;
                uni.showLoading({
                    title: '断开蓝牙连接中...'
                });
                return new Promise((resolve) => {
                    uni.closeBLEConnection({
                        deviceId: _this.connectId,
                        success(res) {
                            console.log("断开连接成功:", res)

                            // 断开后必须释放蓝牙资源,不然再点击连接就连不上了
                            // https://uniapp.dcloud.net.cn/api/system/bluetooth.html
                            uni.closeBluetoothAdapter({
                                success() {
                                    console.log('释放蓝牙资源成功')
                                    _this.connected = false;
                                    _this.connectId = null
                                    _this.SERVICE_UUID = null
                                    _this.CHARACTERISTIC_UUID = null;
                                    _this.inited = false;
                                    _this.received = false;

                                    // 延时下保险点
                                    setTimeout(() => {
                                        uni.hideLoading();
                                        resolve();
                                    }, 600)
                                },
                                fail() {
                                    uni.hideLoading();
                                }
                            })

                        },
                        fail(err) {
                            uni.hideLoading();
                            console.log('断开连接失败:', err)
                            uni.showModal({
                                title: '失败提示',
                                content: "断开连接失败 " + err.errMsg,
                                showCancel: false
                            });
                        }
                    })
                })
            },
            /*-----------------------------------------------------------------------*/
            // 获取蓝牙服务UUID等信息
            getServices() {
                uni.getBLEDeviceServices({
                    deviceId: this.connectId, // 设备ID
                    success(res) {
                        console.log(res);
                    },
                    fail(err) {
                        console.error(err);
                    }
                });
            },
            /*-----------------------------------------------------------------------*/
            // 获取指定服务下的特性
            getCharacteristics() {
                uni.getBLEDeviceCharacteristics({
                    deviceId: this.connectId, // 设备ID
                    serviceId: SERVICE_UUID, // 服务UUID
                    success(res) {
                        console.log(res);
                    },
                    fail(err) {
                        console.error(err);
                    }
                });
            },
            /*-----------------------------------------------------------------------*/
            /*接收外设设备发来的具有通知属性服务及其特性*/
            // ArrayBuffer转16进度字符串示例
            ab2hex(buffer) {
                const hexArr = Array.prototype.map.call(
                    new Uint8Array(buffer),
                    function(bit) {
                        return ('00' + bit.toString(16)).slice(-2);
                    }
                );
                return hexArr.join('');
            },

            // 将16进制的内容转成我们看得懂的字符串内容
            hexCharCodeToStr(hexCharCodeStr) {
                var trimedStr = hexCharCodeStr.trim();
                var rawStr =
                    trimedStr.substr(0, 2).toLowerCase() === '0x' ?
                    trimedStr.substr(2) :
                    trimedStr;
                var len = rawStr.length;
                if (len % 2 !== 0) {
                    alert('存在非法字符!');
                    return '';
                }
                var curCharCode;
                var resultStr = [];
                for (var i = 0; i < len; i = i + 2) {
                    curCharCode = parseInt(rawStr.substr(i, 2), 16);
                    resultStr.push(String.fromCharCode(curCharCode));
                }
                return resultStr.join('');
            },

            // 监听到消息变化时回调处理
            listenValueChange() {
                console.log("对接收的信息做处理");
                uni.onBLECharacteristicValueChange(res => {
                    // 结果
                    console.log(res);

                    // 结果里有个value值,该值为 ArrayBuffer 类型,所以在控制台无法用肉眼观察到,必须将该值转换为16进制
                    let resHex = this.ab2hex(res.value);
                    //console.log(resHex);

                    // 最后将16进制转换为ascii码,就能看到对应的结果
                    let jsonString = this.hexCharCodeToStr(resHex);
                    console.log(jsonString);
                    console.log(typeof(jsonString));
                    this.received = jsonString;
                    //JSON
                    let jsonObject = JSON.parse(jsonString);
                    console.log(jsonObject.sensor); // 输出   
                    console.log(jsonObject.value); // 输出  
                });
            },
            // 开启消息监听已知UUID的特性,由外围蓝牙设备发出
            notify() {
                const _this = this;
                uni.notifyBLECharacteristicValueChange({
                    deviceId: this.connectId, // 设备ID
                    serviceId: SERVICE_UUID, // 服务UUID
                    characteristicId: CHARACTERISTIC_UUID_TX, // 特性UUID
                    success(res) {
                        console.log(res);

                        // 接受消息的方法
                        //this.listenValueChange();//TypeError: this.listenValueChange is not a function
                        _this.listenValueChange(); //this作用域
                    },
                    fail(err) {
                        console.error(err);
                    }
                });
            },
            /*-----------------------------------------------------------------------*/
            // 向外围蓝牙设备发送数据,指定服务及其特性的UUID
            send() {
                // 向蓝牙设备发送一个0x00的16进制数据
                let jsonObject = {
                    name: "John",
                    age: -30,
                    city: "New York"
                };
                let jsonString = JSON.stringify(jsonObject);


                const buffer = new ArrayBuffer(jsonString.length);
                const dataView = new DataView(buffer);
                // dataView.setUint8(0, 0)

                for (var i = 0; i < jsonString.length; i++) {
                    dataView.setUint8(i, jsonString.charAt(i).charCodeAt());
                }

                uni.writeBLECharacteristicValue({
                    deviceId: this.connectId, // 设备ID
                    serviceId: SERVICE_UUID, // 服务UUID
                    characteristicId: CHARACTERISTIC_UUID_RX, // 特性UUID
                    value: buffer,
                    success(res) {
                        console.log(res);
                    },
                    fail(err) {
                        console.error(err);
                    }
                });
            },

            /*-----------------------------------------------------------------------*/
        }, // end of methods
    } // end of export default 
</script>


<style>
    .scan-btn {
        width: 200px;
        background-color: skyblue;
        margin-top: 12px;

    }

    .dev-list {
        border: 1px solid #ccc;
        margin: 6px;
        padding: 6px;
        box-sizing: border-box;
        min-height: 200px;
        background-color: #efefef;
    }

    .deviceItem {
        padding: 12px 0px;
        box-sizing: border-box;
        border-bottom: 1px solid #ccc;
        display: flex;
        flex-wrap: wrap;
        justify-content: space-between;
    }

    .deviceItem span {
        padding: 3px;
        flex-basis: 50%;
        box-sizing: border-box;
        overflow: hidden;
        text-overflow: ellipsis;
    }

    .deviceItem a {
        color: blue;
        width: 100%;
        text-align: right;
        font-weight: 600;
    }

    .msg {
        padding: 6px;
        display: flex;
        align-items: center;
    }

    .msg input {
        border: 1px solid #ccc;
        height: 40px;
    }

    .msg button {
        flex: 1;
        margin-left: 12px;
    }
</style>
View Code
复制代码

 参考:

https://blog.csdn.net/qq_44741577/article/details/139538739

https://blog.csdn.net/weixin_44787578/article/details/139134547

https://baijiahao.baidu.com/s?id=1728894909325212898

BLE常见的16位UUID

https://www.cnblogs.com/yanye0xcc/p/15872646.html

 

 

posted @   辛河  阅读(300)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示