嘿,大家好!咱们今天来聊聊如何在 Vue 应用里玩转硬件设备,用 WebUSB
或者 WebBluetooth
让你的网页应用直接跟硬件“勾搭”上。这感觉就像给你的浏览器装了个“读心术”,能操控现实世界的东西,是不是想想就有点小激动?
开场白:别害怕,硬件交互没那么神秘!
很多人一听到“硬件交互”,就觉得这玩意儿高深莫测,得是电子工程的大佬才能玩得转。其实不然!WebUSB
和 WebBluetooth
已经把底层的复杂性封装得很好,咱们前端工程师也能轻松上手。它们就像两扇门,一扇通往 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
中的 requestUSBDevice
和 sendDataToUSB
函数。
// 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)
: 这个函数用于从蓝牙设备读取数据。serviceUUID
和characteristicUUID
是蓝牙服务的 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 的亮灭。
第四部分:总结与展望
今天我们一起探索了 WebUSB
和 WebBluetooth
的世界,学习了如何用 Vue 应用与硬件设备进行交互。虽然这只是冰山一角,但希望能给你打开一扇新的大门。
WebUSB
和 WebBluetooth
的应用场景非常广泛,例如:
- 物联网 (IoT): 控制智能家居设备,读取传感器数据。
- 医疗健康: 连接心率监测器、血糖仪等设备,实现远程健康监测。
- 工业自动化: 控制机器人、机械臂等设备,提高生产效率。
- 游戏: 连接游戏手柄、VR 设备,提供更沉浸式的游戏体验。
随着 Web 技术的不断发展,WebUSB
和 WebBluetooth
将会变得越来越普及,为我们带来更多的可能性。
结束语:勇敢探索,创造未来!
硬件交互不再是少数人的专属技能,只要你敢于尝试,就能用 Web 技术创造出令人惊叹的应用!希望今天的讲座能激发你的灵感,让你在硬件交互的道路上越走越远。
祝大家编程愉快!