uniapp快速回顾,新学websocket连接和BLE连接

Uni APP的学习

官方文档

uni-app官网 (dcloud.net.cn)

任何的博客都不如官方文档

一、快速复习

文件结构

main.js

  • 功能:项目的入口文件,初始化 Vue 实例。
    App.vue
  • 功能:根组件,包含应用的基本结构和全局样式。
    manifest.json
  • 功能:应用的全局配置文件,包含应用名称、图标、平台特定配置等信息。
    pages.json
  • 功能:配置应用的页面路由、导航栏、选项卡等信息。
    uni.scss
  • 功能:全局的 SCSS 样式文件,可以在整个项目中使用。
    components/
  • 功能:用于存放项目中的可复用组件。
    pages/
  • 功能:用于存放项目的页面,每个页面通常有一个独立的目录和对应的 .vue 文件。
    static/
  • 功能:用于存放项目中的静态资源,如图片、字体等。
    uni_modules/
  • 功能:用于存放 UniApp 插件模块。
    store/
  • 功能:用于存放 Vuex 状态管理的相关文件。
    utils/
  • 功能:用于存放工具函数和公共方法。
    unpackage/
  • 功能:用于存放编译后的文件和生成的包。包含生成的各个平台的编译文件,是项目发布和运行的产物目录

为什么每个页面都使用template包围

每个页面都是一个组件:

  • 在 UniApp 中,一个页面本质上是一个独立的 Vue 组件。它包含了自己的 <template><script><style>,因此可以独立运行。
  • 由于 Vue 组件的特性,这些页面也可以在其他地方被当作子组件使用。
    组件复用:
  • 你可以将一个页面或组件嵌套在另一个页面或组件中,复用其内容和逻辑。例如,你可以在一个主页面中引入一个通用的头部或底部组件。
  • 通过在不同页面之间复用组件,你可以保持代码的简洁,并减少重复劳动。
    组件之间的通信:
  • 当你将一个页面作为组件嵌套在另一个页面中时,你可以通过 props 传递数据,或者通过事件 $emit$on 来实现父子组件之间的通信。
  • 这使得页面之间可以互相传递数据,形成一个有机的整体。

view标签

<view> 标签在 UniApp 中是一个非常重要且常用的标签。它在 UniApp 中起到了类似于 HTML 中 <div> 标签的作用,用于构建页面的基本布局和结构。下面是对 <view> 标签的详细讲解:

1. 基本功能

  • <view> 标签是 UniApp 中的一个容器元素,用来包含其他的视图元素或者组件。
  • 你可以将 <view> 标签理解为一个通用的布局容器,类似于 HTML 中的 <div> 标签。

2. 属性

<view> 标签支持很多属性,下面是一些常见的属性:

  • class: 用于为 <view> 添加样式类,通过 CSS 进行样式控制。
  • style: 直接内联样式设置,例如 style="margin:10px; padding:5px;"
  • id: 为 <view> 指定唯一标识符,用于在 JS 代码中进行操作。
  • hover-class: 当用户触摸时会给 <view> 添加的样式类。
  • hover-start-time: 按住时长达到多少毫秒才显示 hover 状态,默认 50 毫秒。
  • hover-stay-time: 手指松开后,保留 hover 状态的时长,默认 400 毫秒。

3. 使用示例

下面是一个简单的示例,展示如何使用 <view> 标签来构建一个页面的布局:

<template>
  <!-- 页面根容器,使用 Flex 布局来组织页面结构 -->
  <view class="container">
    <!-- 头部区域 -->
    <view 
      class="header"      <!-- 应用样式类 "header" -->
      hover-class="hover-effect"  <!-- 用户触摸时,应用 "hover-effect" 样式 -->
    >
      <text>这是头部(带有hover效果)</text> <!-- 显示在头部的文本内容 -->
    </view>
    
    <!-- 主要内容区域,带有点击事件 -->
    <view 
      class="content"      <!-- 应用样式类 "content" -->
      @click="handleClick" <!-- 点击时触发 handleClick 方法 -->
      hover-class="hover-effect" <!-- 用户触摸时,应用 "hover-effect" 样式 -->
      hover-start-time="100"     <!-- 用户触摸 100 毫秒后,开始显示 hover 效果 -->
      hover-stay-time="300"      <!-- 松开触摸后,hover 效果持续 300 毫秒 -->
      id="main-content"    <!-- 设置唯一标识符为 "main-content" -->
    >
      <text>点击这里触发事件</text> <!-- 显示在内容区域的文本内容 -->
    </view>
    
    <!-- 底部区域,使用动态内联样式 -->
    <view 
      class="footer"       <!-- 应用样式类 "footer" -->
      style="background-color: #00aaff;"  <!-- 内联样式,设置背景颜色为蓝色 -->
      :style="{ height: dynamicHeight + 'px' }" <!-- 动态设置高度,使用 Vue 的绑定语法 -->
    >
      <text>这是底部(背景颜色通过style设置)</text> <!-- 显示在底部的文本内容 -->
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      dynamicHeight: 50 // 动态设置底部区域的高度为 50 像素
    };
  },
  methods: {
    handleClick() {
      // 点击事件的处理逻辑
      console.log('内容区域被点击');
    }
  }
};
</script>

<style>
/* 容器的样式:设置为 Flex 布局,子元素按垂直方向排列,容器高度占满页面 */
.container {
  display: flex;          /* 启用弹性布局 */
  flex-direction: column; /* 子元素垂直排列(从上到下) */
  height: 100%;           /* 容器高度占满父容器或视口 */
}

/* 头部的样式 */
.header {
  background-color: #f8f8f8; /* 设置背景颜色为浅灰色 */
  padding: 10px;             /* 内边距为 10 像素 */
  text-align: center;        /* 文本居中对齐 */
}

/* 内容区域的样式 */
.content {
  flex: 1;                    /* 占据容器中剩余的所有空间 */
  background-color: #ffffff;  /* 设置背景颜色为白色 */
  padding: 20px;              /* 内边距为 20 像素 */
}

/* 底部的样式 */
.footer {
  padding: 10px;              /* 内边距为 10 像素 */
  text-align: center;         /* 文本居中对齐 */
}

/* 用户触摸时的 hover 效果样式 */
.hover-effect {
  background-color: #e0e0e0;  /* 设置触摸时的背景颜色为浅灰色 */
}
</style>

在这个例子中:

  • hover-class:
    • 作用:为 <view> 组件添加一个在用户触摸时显示的样式类。通常用于按钮或可点击的区域,提供视觉反馈。
    • 示例:hover-class="hover-effect",当用户点击或触摸时,hover-effect 样式会被应用。
      hover-start-time:
    • 作用:设置用户触摸多少毫秒后,开始显示 hover-class
    • 示例:hover-start-time="100",用户触摸 100 毫秒后,开始显示 hover-class 的效果。
      hover-stay-time:
    • 作用:设置用户松开触摸后,hover-class 的效果持续多久(毫秒)。
    • 示例:hover-stay-time="300",在用户松开触摸后,hover-class 的效果会持续 300 毫秒。
      id:
    • 作用:为 <view> 组件设置唯一的标识符,可以用于在 JS 中进行 DOM 操作。
    • 示例:id="main-content",你可以通过 this.$refs['main-content'] 来引用这个元素。
      style:
    • 作用:内联样式直接应用于 <view> 元素,用于设置动态样式。
    • 示例:style="background-color: #00aaff;"<view> 设置了蓝色背景。 :style="{ height: dynamicHeight + 'px' }" 使用 Vue 的动态绑定来设置高度。
      @click:
    • 作用:绑定点击事件,当 <view> 被点击时触发对应的事件处理函数。
    • 示例:@click="handleClick",点击 content 区域时,将调用 handleClick 方法。
      class:
    • 作用:应用定义在 <style> 中的样式类,用于控制 <view> 的外观和布局。
    • 示例:class="header" 应用了 .header 中定义的样式。

4. 为什么使用 <view> 而不是 <div>

  • 跨平台支持: <view> 标签在所有 UniApp 支持的平台上都可以正常使用,而 <div> 是 HTML 元素,不适用于小程序、App 等其他平台。
  • 性能优化: UniApp 对 <view> 标签做了许多平台优化,可以更好地适应移动端设备,提供更流畅的用户体验。
  • 样式控制: <view> 标签内的样式和布局在不同平台之间更一致,使用 <view> 可以减少跨平台开发时的样式兼容性问题。

5. 常见的使用场景

  • 布局容器: 创建页面的基础结构和布局。
  • 点击区域: 配合 hover-class 等属性,实现点击效果。
  • 数据展示: 包裹和组织其他元素,如文本、图片、列表等.

flex布局

1. 基础概念

  • Flex 容器(Flex Container): 包含子元素(Flex 项目)的父容器,通常通过设置 display: flex; 来定义。
  • Flex 项目(Flex Items): 容器内的所有直接子元素,它们将自动成为弹性布局的一部分,可以自由地根据容器的规则进行排列和对齐。

2. 基本属性

在 Flexbox 布局中,最重要的属性主要有以下几类:

2.1. 容器属性(应用于 Flex 容器)
  1. display: flex;
    • 将一个元素定义为 Flex 容器,容器内的子元素将变成 Flex 项目。
  2. flex-direction
    • 决定 Flex 项目的排列方向。
      • row: 默认值,从左到右水平排列。
      • row-reverse: 从右到左水平排列。
      • column: 从上到下垂直排列。
      • column-reverse: 从下到上垂直排列。
  3. justify-content
    • 控制 Flex 项目在主轴(flex-direction 方向)的对齐方式。
      • flex-start: 靠主轴的起点对齐。
      • flex-end: 靠主轴的终点对齐。
      • center: 居中对齐。
      • space-between: 项目之间的间距相等,首尾项目与容器边缘对齐。
      • space-around: 项目之间的间距相等,项目和容器边缘之间留有一半的间距。
  4. align-items
    • 控制 Flex 项目在交叉轴(与主轴垂直的方向)的对齐方式。
      • stretch: 默认值,项目在交叉轴方向上拉伸以填充容器。
      • flex-start: 靠交叉轴的起点对齐。
      • flex-end: 靠交叉轴的终点对齐。
      • center: 在交叉轴方向居中对齐。
      • baseline: 项目基线对齐。
  5. flex-wrap
    • 决定如果容器空间不足,Flex 项目是否换行。
      • nowrap: 默认值,不换行。
      • wrap: 换行,项目在多行中排列。
      • wrap-reverse: 反向换行。
2.2. 项目属性(应用于 Flex 项目)
  1. flex
    • 简写属性,用于指定 Flex 项目的可伸缩性。
      • flex: 1; 表示项目将占据容器的所有可用空间,多个项目之间将按比例分配空间。
  2. align-self
    • 允许单个项目对齐方式与容器的 align-items 属性不同。
  3. order
    • 控制项目的排列顺序,数值越小的项目会排在前面,默认值为 0

二、websocket的连接

函数介绍

1. connect

  • 功能:建立 WebSocket 连接,并设置各种事件回调来处理连接成功、收到消息、连接关闭和连接出错的情况。
  • 核心操作:通过 uni.connectSocket 方法创建 WebSocket 连接,成功后发送初始数据。

2. sendMessage

  • 功能:通过 WebSocket 连接向服务器发送文本消息。
  • 核心操作
    • 检查 WebSocket 连接是否处于打开状态。
    • 构造要发送的消息(包含目标客户端 ID 和消息内容)。
    • 通过 socketTask.send 方法发送消息。

3. handleFileChange

  • 功能:处理用户选择的音频文件,并将文件保存在组件的状态中,以便稍后发送。
  • 核心操作
    • 捕获用户选择的文件,并将其存储在 audioFile 变量中。

4. sendAudio

  • 功能:将选择的音频文件通过 WebSocket 连接发送给服务器。
  • 核心操作
    • 使用 FileReader 读取音频文件的内容,并转换为 ArrayBuffer 格式。
  • 通过 socketTask.send 方法逐块发送音频数据,并在发送结束后发送 "EOF" 标志表示文件传输结束。

详细功能介绍

1. connect
  • 功能:启动 WebSocket 连接,是整个应用程序的核心功能。它确保了客户端能够与服务器通信,并在连接成功后立即向服务器发送客户端的身份信息(即 uniqueIdmateUniqueId)。
  • 使用场景:当用户想要连接到服务器进行实时通信时,点击“Connect”按钮触发。
2. sendMessage
  • 功能:允许用户通过 WebSocket 连接向特定的客户端发送消息。这是聊天应用或任何需要发送点对点消息的应用程序的核心功能。
  • 使用场景:当用户输入目标客户端 ID 和消息内容后,点击“Send”按钮发送消息。
3. handleFileChange
  • 功能:捕获用户选择的音频文件,并为后续的音频传输做准备。这个函数确保了音频文件能够正确读取并保存在组件的状态中。
  • 使用场景:当用户选择音频文件时,这个函数被触发,准备好文件以便稍后发送。
4. sendAudio
  • 功能:将音频文件通过 WebSocket 连接发送到服务器。这个函数负责处理音频文件的分块传输,并确保传输的完整性。
  • 使用场景:当用户选择了音频文件并点击“Send Audio”按钮时,触发这个函数开始传输音频数据。

连接函数

connect() {
    // 创建一个 WebSocket 连接,保存到 socketTask 变量中
    this.socketTask = uni.connectSocket({
        url: "wss://erroright.cn/ws", // WebSocket 服务器的地址
        success: () => {
            // 如果 WebSocket 连接创建成功,输出成功日志
            console.log("WebSocket connection created.");
        },
        fail: (error) => {
            // 如果 WebSocket 连接创建失败,输出错误信息
            console.error("Failed to create WebSocket connection:", error);
        }
    });

    // 当 WebSocket 连接成功时触发的事件回调
    this.socketTask.onOpen(() => {
        // 输出调试信息,表示已经成功连接到 WebSocket 服务器
        console.log("Connected to WebSocket server.");

        // 构造发送给服务器的初始数据,包含客户端的唯一 ID 和配对客户端的唯一 ID
        let data = {
            send_id: this.uniqueId,      // 客户端的唯一 ID
            receive_id: this.mateUniqueId // 配对客户端的唯一 ID
        };

        // 输出调试信息,显示即将发送的初始数据
        console.log("Sending initial data:", data);

        // 通过 WebSocket 连接发送初始数据
        this.socketTask.send({
            data: JSON.stringify(data), // 将数据转换为 JSON 字符串格式发送
            fail: (error) => {
                // 如果发送初始数据失败,输出错误信息
                console.error("Failed to send initial data:", error);
            }
        });
    });

    // 当从服务器接收到消息时触发的事件回调
    this.socketTask.onMessage((event) => {
        // 输出调试信息,显示接收到的消息内容
        console.log("Received message from server:", event.data);

        // 将接收到的消息添加到 messages 数组中,以便在页面中显示
        this.messages.push(event.data);
    });

    // 当 WebSocket 连接关闭时触发的事件回调
    this.socketTask.onClose(() => {
        // 输出调试信息,表示 WebSocket 连接已关闭
        console.log("Disconnected from WebSocket server.");
    });

    // 当 WebSocket 连接出错时触发的事件回调
    this.socketTask.onError((error) => {
        // 输出调试信息,显示具体的错误内容
        console.error("WebSocket error occurred:", error);
    });
}

发送信息

sendMessage() {
                // 检查 WebSocket 连接是否存在并且处于打开状态
                if (this.socketTask && this.socketTask.readyState === WebSocket.OPEN) {
                    // 构造要发送的消息对象,包含目标客户端 ID 和消息内容
                    let message = JSON.stringify({
                        content: this.messageText  // 要发送的消息内容
                    });
            
                    // 输出调试信息,显示即将发送的消息内容
                    console.log("Sending message:", message);
            
                    // 通过 WebSocket 连接发送消息
                    this.socketTask.send({
                        data: message,  // 发送的消息数据
                        success: () => {
                            // 发送成功后清空消息输入框
                            this.messageText = '';
                        },
                        fail: (error) => {
                            // 如果消息发送失败,输出错误信息
                            console.error("Failed to send message:", error);
                        }
                    });
                } else {
                    // 如果 WebSocket 未连接或状态不正确,输出错误信息
                    console.error("WebSocket is not connected or is in an invalid state.");
                }
            },

捕获音频文件,为后续音频传输做准备

handleFileChange(event) {
    // 从事件对象 event 中获取用户选择的文件
    // event.target.files 是一个 FileList 对象,包含了用户选择的所有文件
    // [0] 代表获取第一个文件对象,因为通常用户只选择一个文件
    this.audioFile = event.target.files[0];

    // 输出调试信息,显示用户选择的文件的名称
    // 通过 this.audioFile.name 获取文件的名称,确认用户选择的文件正确无误
    console.log("Selected audio file:", this.audioFile.name);
}

发送音频文件给服务器

sendAudio() {
    // 检查是否选择了音频文件
    if (!this.audioFile) {
        // 如果没有选择音频文件,提示用户先选择文件
        uni.showToast({
            title: 'Please select an audio file first', // 显示的提示信息
            icon: 'none' // 不显示图标
        });
        return; // 结束函数执行
    }

    // 创建一个 FileReader 对象,用于读取文件内容
    let reader = new FileReader();

    // 定义 FileReader 的 onload 回调,当文件读取完成时触发
    reader.onload = (event) => {
        // 获取读取的文件内容,作为 ArrayBuffer 对象
        let arrayBuffer = event.target.result;

        // 将 ArrayBuffer 转换为 Uint8Array,这样可以逐字节处理数据
        let bytes = new Uint8Array(arrayBuffer);

        // 输出调试信息,显示即将发送的音频数据
        console.log("Sending audio data:", bytes);

        // 通过 WebSocket 发送音频数据
        this.socketTask.send({
            data: bytes.buffer, // 将 Uint8Array 的缓冲区发送
            success: () => {
                // 音频数据发送成功后,再发送一个 "EOF" 标志,表示传输结束
                this.socketTask.send({
                    data: "EOF", // 发送 "EOF" 表示文件传输结束
                    success: () => {
                        console.log("EOF sent."); // 输出 "EOF" 发送成功的日志
                    },
                    fail: (error) => {
                        console.error("Failed to send EOF:", error); // 输出 "EOF" 发送失败的错误信息
                    }
                });
            },
            fail: (error) => {
                console.error("Failed to send audio data:", error); // 输出音频数据发送失败的错误信息
            }
        });
    };

    // 开始异步读取音频文件的内容,并将其转换为 ArrayBuffer 格式
    reader.readAsArrayBuffer(this.audioFile);
}

三、蓝牙

1. initBlue

  • 功能:初始化蓝牙模块。
  • 操作
    • 调用 uni.openBluetoothAdapter 打开蓝牙适配器。
  • 如果初始化成功,调用 startBluetoothDevicesDiscovery 开始搜索蓝牙设备。

2. startBluetoothDevicesDiscovery

  • 功能:启动蓝牙设备的搜索。
  • 操作
    • 调用 uni.startBluetoothDevicesDiscovery 开始搜索蓝牙设备。
  • 如果搜索成功,设置蓝牙设备发现的回调函数 onBluetoothDeviceFound

3. onBluetoothDeviceFound

  • 功能:处理发现的蓝牙设备。
  • 操作
    • 当发现蓝牙设备时,检查设备名称是否与 deviceName 匹配。
  • 如果匹配,保存设备的 deviceId 并调用 createBLEConnection 尝试连接设备。

4. createBLEConnection

  • 功能:创建蓝牙低功耗设备 (BLE) 的连接。
  • 操作
    • 调用 uni.createBLEConnection 连接到指定的蓝牙设备。
    • 如果连接成功,更新连接状态为已连接 (isConnected: true)。
    • 延迟 3 秒后调用 getBLEDeviceServices 获取设备的服务列表。

5. getBLEDeviceServices

  • 功能:获取连接设备的服务列表。
  • 操作
    • 调用 uni.getBLEDeviceServices 获取设备的服务。
    • 如果成功,遍历服务列表并检查是否包含指定的服务 UUID (SERVICE_UUID)。
    • 如果找到匹配的服务,调用 getBLEDeviceCharacteristics 获取该服务的特性。
    • 如果获取服务失败或服务列表为空,最多重试三次。

6. getBLEDeviceCharacteristics

  • 功能:获取指定服务的特性。
  • 操作
    • 调用 uni.getBLEDeviceCharacteristics 获取服务的特性列表。
    • 如果成功,检查特性是否支持写操作,并将其 UUID 保存到 CHARACTERISTIC_UUID
    • 如果没有找到支持写操作的特性,输出错误信息。

7. sendDataToBluetooth

  • 功能:发送数据到蓝牙设备。
  • 操作
    • 首先检查是否已连接到蓝牙设备。
  • 如果连接正常,调用 sendDataInChunks 将数据分块发送到蓝牙设备。

8. sendDataInChunks

  • 功能:将数据分块发送到蓝牙设备。
  • 操作
    • 根据设备的 MTU 大小(这里假设为 517 字节)将数据分块。
  • 对每个数据块,创建 ArrayBuffer 并转换为字节数据后,通过 uni.writeBLECharacteristicValue 发送给设备。

9. action

  • 功能:发送 “over” 命令到蓝牙设备,表示数据传输完成或其他特定操作。
  • 操作
    • 类似于 sendDataToBluetooth,但是这里发送的是 “over” 字符串。
<template>
    <view class="container">
        <view class="intro">蓝牙消息发送示例。</view>
        <button @click="initBlue">连接蓝牙</button>
        <button @click="sendDataToBluetooth">发送数据</button>
        <button @click="action">开始运行</button>
    </view>
</template>

<script>
    export default {
        data() {
            return {
                deviceName: "MiniBay", // 要连接的设备名称
                deviceId: null, // 设备 ID
                SERVICE_UUID: "4FAFC201-1FB5-459E-8FCC-C5C9C331914B", // 服务的 UUID (大写)
                CHARACTERISTIC_UUID: "BEB5483E-36E1-4688-B7F5-EA07361B26A8", // 特性的 UUID (大写)
                dataToSend:"{10, 10, 200},{20, 10, 200},{10, 20, 200},{30, 10, 200},{25, 25, 100},{10, 30, 200},{20, 20, 100},{30, 30, 100},{35, 35, 100},{40, 40, 100},{45, 45, 100},{50, 50, 100},{55, 55, 100},{60, 60, 100},{65, 65, 100},{70, 70, 100},{75, 75, 100},{80, 80, 100},{85, 85, 200},{90, 90, 200},{95, 95, 200},{90, 90, 200},{95, 95, 200},{90, 90, 200},{95, 95, 200},{90, 90, 200},{95, 95, 200},{90, 90, 200},{95, 95, 200},{90, 90, 200},{95, 95, 200},{90, 90, 200},{45, 45, 200},{0, 0, 0}",
                isConnected: false // 用于跟踪蓝牙是否已连接
            }
        },
        methods: {
            initBlue() {
                uni.openBluetoothAdapter({
                    success: res => {
                        console.log('初始化蓝牙成功');
                        this.startBluetoothDevicesDiscovery();
                    },
                    fail: err => {
                        console.log('初始化蓝牙失败');
                        console.error(err);
                    }
                });
            },
            startBluetoothDevicesDiscovery() {
                uni.startBluetoothDevicesDiscovery({
                    success: res => {
                        console.log('开始搜索设备...');
                        this.onBluetoothDeviceFound();
                    },
                    fail: err => {
                        console.log('搜索设备失败');
                        console.error(err);
                    }
                });
            },
            onBluetoothDeviceFound() {
                uni.onBluetoothDeviceFound(devices => {
                    devices.devices.forEach(device => {
                        if (device.name === this.deviceName) {
                            console.log('找到设备:', device);
                            this.deviceId = device.deviceId;
                            this.createBLEConnection();
                        }
                    });
                });
            },
            createBLEConnection() {
                if (this.deviceId) {
                    uni.createBLEConnection({
                        deviceId: this.deviceId,
                        success: res => {
                            console.log('连接设备成功');
                            this.isConnected = true; // 更新连接状态
                            // 延迟以确保设备服务准备就绪
                            setTimeout(() => {
                                this.getBLEDeviceServices();
                            }, 3000);
                        },
                        fail: err => {
                            console.log('连接设备失败');
                            this.isConnected = false; // 确保连接失败时状态更新
                            console.error(err);
                        }
                    });
                }
            },
            getBLEDeviceServices(retryCount = 0) {
                uni.getBLEDeviceServices({
                    deviceId: this.deviceId,
                    success: res => {
                        if (res.services && res.services.length > 0) {
                            console.log('获取服务成功:', res.services);
                            res.services.forEach((service, index) => {
                                console.log(`服务 ${index + 1}: UUID = ${service.uuid}`);
                            });

                            // 检查是否找到特定的服务
                            const service = res.services.find(s => s.uuid === this.SERVICE_UUID);
                            if (service) {
                                this.getBLEDeviceCharacteristics(service.uuid);
                            } else {
                                console.error('未找到指定的服务');
                            }
                        } else {
                            console.error('服务列表为空或未找到服务');
                            if (retryCount < 3) { // 尝试重试三次
                                console.log(`重试获取服务 (${retryCount + 1}/3)`);
                                setTimeout(() => {
                                    this.getBLEDeviceServices(retryCount + 1);
                                }, 3000); // 每次延迟3秒再重试
                            }
                        }
                    },
                    fail: err => {
                        console.log('获取服务失败');
                        console.error(err);
                    }
                });
            },
            getBLEDeviceCharacteristics(serviceId) {
                uni.getBLEDeviceCharacteristics({
                    deviceId: this.deviceId,
                    serviceId: serviceId,
                    success: res => {
                        console.log('获取特性成功:', res.characteristics);
                        // 检查特性是否支持写操作
                        const writableCharacteristic = res.characteristics.find(char => char.properties.write);
                        if (writableCharacteristic) {
                            this.CHARACTERISTIC_UUID = writableCharacteristic.uuid;
                        } else {
                            console.error('未找到支持写操作的特性');
                        }
                    },
                    fail: err => {
                        console.log('获取特性失败');
                        console.error(err);
                    }
                });
            },
            sendDataToBluetooth() {
                if (!this.isConnected) {
                    console.error('未连接到蓝牙设备');
                    return;
                }
                const data = this.dataToSend;
                this.sendDataInChunks(data);
            },
            sendDataInChunks(data) {
                const maxChunkSize = 517; // 依据设备的 MTU,20 是默认蓝牙包大小
                for (let i = 0; i < data.length; i += maxChunkSize) {
                    const chunk = data.slice(i, i + maxChunkSize);
                    const buffer = new ArrayBuffer(chunk.length);
                    const dataView = new DataView(buffer);
                    for (let j = 0; j < chunk.length; j++) {
                        dataView.setUint8(j, chunk.charCodeAt(j));
                    }
                    uni.writeBLECharacteristicValue({
                        deviceId: this.deviceId,
                        serviceId: this.SERVICE_UUID,
                        characteristicId: this.CHARACTERISTIC_UUID,
                        value: buffer,
                        success: res => {
                            console.log('数据发送成功:', chunk);
                        },
                        fail: err => {
                            console.log('数据发送失败');
                            console.error(err);
                        }
                    });
                }
            },
            action() {
                if (!this.isConnected) {
                    console.error('未连接到蓝牙设备');
                    return;
                }
                const formattedData = "over";
                const buffer = new ArrayBuffer(formattedData.length);
                const dataView = new DataView(buffer);
                for (let i = 0; i < formattedData.length; i++) {
                    dataView.setUint8(i, formattedData.charCodeAt(i));
                }
                uni.writeBLECharacteristicValue({
                    deviceId: this.deviceId,
                    serviceId: this.SERVICE_UUID,
                    characteristicId: this.CHARACTERISTIC_UUID,
                    value: buffer,
                    success: res => {
                        console.log('数据发送成功:', formattedData);
                    },
                    fail: err => {
                        console.log('数据发送失败');
                        console.error(err);
                    }
                });
            }
        }
    }
</script>

<style>
    .container {
        padding: 20px;
        font-size: 14px;
        line-height: 24px;
    }
</style>

  • 19
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

weighless1129

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值