如何在 Vue 应用中,实现一个基于 `WebUSB` 或 `WebBluetooth` 的硬件设备交互功能?

嘿,大家好!咱们今天来聊聊如何在 Vue 应用里玩转硬件设备,用 WebUSB 或者 WebBluetooth 让你的网页应用直接跟硬件“勾搭”上。这感觉就像给你的浏览器装了个“读心术”,能操控现实世界的东西,是不是想想就有点小激动?

开场白:别害怕,硬件交互没那么神秘!

很多人一听到“硬件交互”,就觉得这玩意儿高深莫测,得是电子工程的大佬才能玩得转。其实不然!WebUSBWebBluetooth 已经把底层的复杂性封装得很好,咱们前端工程师也能轻松上手。它们就像两扇门,一扇通往 USB 设备的世界,另一扇通往蓝牙设备的世界,而我们只需要学会怎么开门、怎么跟里面的“居民”打交道就行。

第一部分:WebUSB 探险之旅

WebUSB 允许你的网页应用直接通过 USB 接口与硬件设备通信。这意味着你可以控制 LED 灯的闪烁、读取传感器数据,甚至是控制复杂的机械臂!

1. 准备工作:装备你的 Vue 项目

首先,我们需要一个 Vue 项目。如果你还没有,可以用 Vue CLI 快速搭建一个:

vue create webusb-demo
cd webusb-demo

然后,我们需要一些工具函数来简化 WebUSB 的操作。创建一个 src/utils/usb.js 文件,稍后我们会往里面添加代码。

2. 找到你的“伙伴”:设备连接

核心代码来啦!我们需要监听按钮点击事件,当用户点击“连接设备”按钮时,弹出设备选择窗口。

<template>
  <div>
    <button @click="connectDevice">连接设备</button>
    <p v-if="device">已连接设备:{{ device.productName }}</p>
    <p v-else>未连接任何设备</p>
    <button v-if="device" @click="sendData">发送数据</button>
  </div>
</template>

<script>
import { ref } from 'vue';
import { requestUSBDevice, sendDataToUSB } from '@/utils/usb'; // 稍后实现

export default {
  setup() {
    const device = ref(null);

    const connectDevice = async () => {
      try {
        device.value = await requestUSBDevice(); // 使用封装的函数
        console.log('已连接设备:', device.value);
      } catch (error) {
        console.error('连接设备失败:', error);
        alert('连接设备失败,请检查设备是否已连接并允许访问。');
      }
    };

    const sendData = async () => {
      if (!device.value) return;
      try {
        await sendDataToUSB(device.value, new Uint8Array([0x01, 0x02, 0x03])); // 发送一些数据
        console.log('数据发送成功!');
      } catch (error) {
        console.error('数据发送失败:', error);
        alert('发送数据失败,请检查设备状态。');
      }
    };

    return {
      device,
      connectDevice,
      sendData,
    };
  },
};
</script>

3. USB 工具函数:src/utils/usb.js

接下来,我们来实现 src/utils/usb.js 中的 requestUSBDevicesendDataToUSB 函数。

// src/utils/usb.js

// 1. 请求 USB 设备
export async function requestUSBDevice(filters = []) {
  try {
    const device = await navigator.usb.requestDevice({ filters });
    await device.open();

    // 查找配置并激活
    if (device.configuration === null) {
      await device.selectConfiguration(1); // 假设配置ID为1,根据你的设备配置修改
    }

    // 查找接口并声明独占
    await device.claimInterface(0); // 假设接口ID为0,根据你的设备配置修改

    return device;
  } catch (error) {
    console.error('请求 USB 设备失败:', error);
    throw error; // 抛出错误,让调用者处理
  }
}

// 2. 发送数据到 USB 设备
export async function sendDataToUSB(device, data, endpointNumber = 1) {
  // 假设端点号为1,根据你的设备配置修改 (OUT 端点)

  try {
    const result = await device.transferOut(endpointNumber, data);
    if (result.status !== 'ok') {
      throw new Error(`数据发送失败,状态码:${result.status}`);
    }
    return result;
  } catch (error) {
    console.error('发送数据到 USB 设备失败:', error);
    throw error;
  }
}

// 3. 从 USB 设备接收数据
export async function receiveDataFromUSB(device, length, endpointNumber = 2) {
  // 假设端点号为2,根据你的设备配置修改 (IN 端点)

  try {
    const result = await device.transferIn(endpointNumber, length);
    if (result.status !== 'ok') {
      throw new Error(`数据接收失败,状态码:${result.status}`);
    }
    return result.data; // 返回 DataView 对象
  } catch (error) {
    console.error('从 USB 设备接收数据失败:', error);
    throw error;
  }
}

代码解释:

  • requestUSBDevice(filters): 这个函数会弹出设备选择窗口,让用户选择要连接的 USB 设备。filters 参数可以用来过滤设备,比如只显示特定厂商的设备。
  • sendDataToUSB(device, data, endpointNumber): 这个函数用于向 USB 设备发送数据。data 是一个 Uint8Array 对象,包含要发送的字节数据。endpointNumber 是 USB 端点的编号,你需要根据你的设备文档来确定。
  • receiveDataFromUSB(device, length, endpointNumber): 这个函数用于从 USB 设备接收数据。length 是要接收的字节数,endpointNumber 是 USB 端点的编号。

4. 关键概念:配置、接口、端点

要理解 WebUSB 的工作原理,你需要了解三个关键概念:

  • 配置 (Configuration): 一个设备可以有多个配置,每个配置定义了设备的不同工作模式。
  • 接口 (Interface): 一个配置可以包含多个接口,每个接口定义了设备的不同功能模块。
  • 端点 (Endpoint): 每个接口可以包含多个端点,每个端点是数据传输的通道。通常,一个接口会有一个输入端点 (IN endpoint) 用于接收数据,和一个输出端点 (OUT endpoint) 用于发送数据。

这些概念可以用一张表来概括:

概念 描述
配置 (Configuration) 设备的整体工作模式,例如高速模式、低功耗模式等。一个设备可以有多个配置。
接口 (Interface) 设备的功能模块,例如键盘接口、鼠标接口、摄像头接口等。一个配置可以包含多个接口。每个接口代表设备的不同功能。
端点 (Endpoint) 数据传输的通道,分为输入端点 (IN endpoint) 和输出端点 (OUT endpoint)。输入端点用于从设备接收数据,输出端点用于向设备发送数据。每个接口可以包含多个端点。

5. 调试技巧:检查你的设备信息

在开发过程中,了解你的 USB 设备的配置、接口和端点信息非常重要。你可以使用 Chrome 浏览器的 chrome://device-log 页面来查看这些信息。连接你的设备,然后在该页面刷新,你应该能看到设备的详细信息。

6. 安全注意事项:HTTPS 是必须的!

WebUSB 只能在 HTTPS 协议下使用,这是为了防止恶意网站滥用 USB 接口。

第二部分:WebBluetooth 蓝牙奇遇记

WebBluetooth 允许你的网页应用通过蓝牙与附近的设备通信。你可以连接智能手表、心率监测器、蓝牙音箱等等。

1. 准备工作:开启你的蓝牙

确保你的电脑或手机已经开启了蓝牙功能。

2. 连接蓝牙设备:开始扫描

<template>
  <div>
    <button @click="connectBluetooth">连接蓝牙设备</button>
    <p v-if="bluetoothDevice">已连接设备:{{ bluetoothDevice.name }}</p>
    <p v-else>未连接任何蓝牙设备</p>
    <button v-if="bluetoothDevice" @click="readData">读取数据</button>
  </div>
</template>

<script>
import { ref } from 'vue';
import { requestBluetoothDevice, readBluetoothData } from '@/utils/bluetooth'; // 稍后实现

export default {
  setup() {
    const bluetoothDevice = ref(null);

    const connectBluetooth = async () => {
      try {
        bluetoothDevice.value = await requestBluetoothDevice({
          filters: [{ services: ['heart_rate'] }], // 过滤只显示心率监测器
        });
        console.log('已连接蓝牙设备:', bluetoothDevice.value);
      } catch (error) {
        console.error('连接蓝牙设备失败:', error);
        alert('连接蓝牙设备失败,请检查设备是否已开启蓝牙并允许访问。');
      }
    };

    const readData = async () => {
      if (!bluetoothDevice.value) return;
      try {
        const data = await readBluetoothData(bluetoothDevice.value, 'heart_rate', 'heart_rate_measurement'); // 读取心率数据
        console.log('读取到的心率数据:', data);
        alert(`心率:${data.getUint8(1)} bpm`);
      } catch (error) {
        console.error('读取数据失败:', error);
        alert('读取数据失败,请检查设备状态。');
      }
    };

    return {
      bluetoothDevice,
      connectBluetooth,
      readData,
    };
  },
};
</script>

3. 蓝牙工具函数:src/utils/bluetooth.js

// src/utils/bluetooth.js

// 1. 请求蓝牙设备
export async function requestBluetoothDevice(options = {}) {
  try {
    const device = await navigator.bluetooth.requestDevice(options);
    const server = await device.gatt.connect(); // 连接 GATT 服务器
    return { device, server };
  } catch (error) {
    console.error('请求蓝牙设备失败:', error);
    throw error;
  }
}

// 2. 读取蓝牙数据
export async function readBluetoothData(deviceInfo, serviceUUID, characteristicUUID) {
  const { device, server } = deviceInfo;
  try {
    const service = await server.getPrimaryService(serviceUUID); // 获取服务
    const characteristic = await service.getCharacteristic(characteristicUUID); // 获取特征
    const value = await characteristic.readValue(); // 读取值
    return value; // 返回 DataView 对象
  } catch (error) {
    console.error('读取蓝牙数据失败:', error);
    throw error;
  }
}

// 3. 写入蓝牙数据
export async function writeBluetoothData(deviceInfo, serviceUUID, characteristicUUID, data) {
  const { device, server } = deviceInfo;
  try {
    const service = await server.getPrimaryService(serviceUUID);
    const characteristic = await service.getCharacteristic(characteristicUUID);
    await characteristic.writeValue(data); // 写入数据
  } catch (error) {
    console.error('写入蓝牙数据失败:', error);
    throw error;
  }
}

代码解释:

  • requestBluetoothDevice(options): 这个函数会弹出设备选择窗口,让用户选择要连接的蓝牙设备。options.filters 参数可以用来过滤设备,比如只显示特定服务的设备(例如,心率监测器)。
  • readBluetoothData(deviceInfo, serviceUUID, characteristicUUID): 这个函数用于从蓝牙设备读取数据。serviceUUIDcharacteristicUUID 是蓝牙服务的 UUID(通用唯一识别码)和特征的 UUID,你需要根据你的设备文档来确定。
  • writeBluetoothData(deviceInfo, serviceUUID, characteristicUUID, data): 这个函数用于向蓝牙设备写入数据。data 是一个 ArrayBuffer 对象,包含要发送的字节数据。

4. 关键概念:GATT、服务、特征

要理解 WebBluetooth 的工作原理,你需要了解三个关键概念:

  • GATT (Generic Attribute Profile): 定义了蓝牙设备如何通过服务和特征来暴露数据和功能。
  • 服务 (Service): 一组相关的特征的集合,例如心率监测服务、电量服务等。
  • 特征 (Characteristic): 一个服务中的一个数据点,例如心率值、电量百分比等。

这些概念可以用一张表来概括:

概念 描述
GATT 蓝牙低功耗 (BLE) 设备使用的协议,定义了设备如何通过服务和特征来组织和传输数据。
服务 (Service) 一组相关的特征的集合,代表设备的一个特定功能或数据类型。例如,心率监测服务包含心率测量特征、传感器位置特征等。每个服务都有一个唯一的 UUID。
特征 (Characteristic) 服务中的一个数据点,代表设备的一个具体属性或功能。例如,心率测量特征包含心率值数据。每个特征都有一个唯一的 UUID,并且可以定义其属性,例如是否可读、可写、是否支持通知等。

5. 调试技巧:使用蓝牙调试工具

在开发过程中,可以使用蓝牙调试工具来查看蓝牙设备的广播数据、服务和特征信息。例如,可以使用 LightBlue (iOS) 或 nRF Connect (Android/iOS) 等应用程序。

6. 安全注意事项:用户许可很重要!

WebUSB 类似,WebBluetooth 也需要用户明确授权才能访问蓝牙设备。

第三部分:实战演练:控制一个 LED 灯

现在,让我们来一个实战演练,用 WebUSB 控制一个 LED 灯的亮灭。你需要一个支持 WebUSB 的 USB LED 灯,或者自己用 Arduino 制作一个。

1. Arduino 代码:

// Arduino 代码

const int ledPin = 13; // LED 连接到数字引脚 13

void setup() {
  pinMode(ledPin, OUTPUT);
  Serial.begin(115200);
}

void loop() {
  if (Serial.available() > 0) {
    int command = Serial.read();
    if (command == '1') {
      digitalWrite(ledPin, HIGH); // 开灯
      Serial.println("LED ON");
    } else if (command == '0') {
      digitalWrite(ledPin, LOW);  // 关灯
      Serial.println("LED OFF");
    }
  }
}

2. Vue 代码:

<template>
  <div>
    <button @click="connectDevice">连接设备</button>
    <p v-if="device">已连接设备:{{ device.productName }}</p>
    <p v-else>未连接任何设备</p>
    <button v-if="device" @click="turnOn">开灯</button>
    <button v-if="device" @click="turnOff">关灯</button>
  </div>
</template>

<script>
import { ref } from 'vue';
import { requestUSBDevice, sendDataToUSB } from '@/utils/usb';

export default {
  setup() {
    const device = ref(null);

    const connectDevice = async () => {
      try {
        device.value = await requestUSBDevice();
        console.log('已连接设备:', device.value);
      } catch (error) {
        console.error('连接设备失败:', error);
        alert('连接设备失败,请检查设备是否已连接并允许访问。');
      }
    };

    const turnOn = async () => {
      if (!device.value) return;
      try {
        await sendDataToUSB(device.value, new Uint8Array([0x31])); // 发送 '1' 的 ASCII 码
        console.log('LED ON');
      } catch (error) {
        console.error('发送数据失败:', error);
        alert('发送数据失败,请检查设备状态。');
      }
    };

    const turnOff = async () => {
      if (!device.value) return;
      try {
        await sendDataToUSB(device.value, new Uint8Array([0x30])); // 发送 '0' 的 ASCII 码
        console.log('LED OFF');
      } catch (error) {
        console.error('发送数据失败:', error);
        alert('发送数据失败,请检查设备状态。');
      }
    };

    return {
      device,
      connectDevice,
      turnOn,
      turnOff,
    };
  },
};
</script>

代码解释:

  • Arduino 代码监听串口数据,当收到 ‘1’ 时点亮 LED,收到 ‘0’ 时熄灭 LED。
  • Vue 代码通过 sendDataToUSB 函数向 USB 设备发送 ‘1’ 或 ‘0’ 的 ASCII 码,从而控制 LED 的亮灭。

第四部分:总结与展望

今天我们一起探索了 WebUSBWebBluetooth 的世界,学习了如何用 Vue 应用与硬件设备进行交互。虽然这只是冰山一角,但希望能给你打开一扇新的大门。

WebUSBWebBluetooth 的应用场景非常广泛,例如:

  • 物联网 (IoT): 控制智能家居设备,读取传感器数据。
  • 医疗健康: 连接心率监测器、血糖仪等设备,实现远程健康监测。
  • 工业自动化: 控制机器人、机械臂等设备,提高生产效率。
  • 游戏: 连接游戏手柄、VR 设备,提供更沉浸式的游戏体验。

随着 Web 技术的不断发展,WebUSBWebBluetooth 将会变得越来越普及,为我们带来更多的可能性。

结束语:勇敢探索,创造未来!

硬件交互不再是少数人的专属技能,只要你敢于尝试,就能用 Web 技术创造出令人惊叹的应用!希望今天的讲座能激发你的灵感,让你在硬件交互的道路上越走越远。

祝大家编程愉快!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注