Bluetooth Low Energy (BLE) communication is a fundamental requirement for many IoT applications. This guide outlines the technical workflow for establishing a BLE connection, discovering services, and managing bidirectional data transmission using uni-app and Vue 3. The logic described here is applicable to Android apps, iOS apps, and WeChat Mini Programs, as the underlying API architecture remains consistent.
Operational Workflow
Unlike standard HTTP requests, BLE operations are asynchronous and event-driven. The communication lifecycle involves five distinct stages:
- Initialization: Activate the host device's Bluetooth adapter.
- Scanning: Broadcast discovery packets to detect peripheral devices.
- Connection: Establish a dedicated link with a target device.
- Service Discovery: Query the device for GATT services and characteristics.
- Interaction: Enable notifications and write binary data to the device.
1. Initializing the Adapter
Before any BLE operation, the adapter must be initialized using uni.openBluetoothAdapter. Subsequent API calls will fail with error code 10000 if this step is skipped or unsuccessful.
function initializeAdapter() {
uni.openBluetoothAdapter({
success() {
console.log('Adapter initialized successfully');
},
fail(error) {
console.error('Initialization failed:', error.errCode);
}
});
}
A common error code is 10001, indicating the Bluetooth radio is currently turned off on the device.
2. Scanning for Devices
Scanning involves starting the discovery process and listening for found devices. Because scanning consumes significant power, it must be stopped explicitly after a connection is established.
const discoveredDevices = ref([]);
function startScan() {
uni.startBluetoothDevicesDiscovery({
allowDuplicatesKey: false,
success() {
uni.onBluetoothDeviceFound((result) => {
result.devices.forEach(device => {
// Filter duplicates by deviceId if necessary
if (!discoveredDevices.value.find(d => d.deviceId === device.deviceId)) {
discoveredDevices.value.push(device);
}
});
});
},
fail(error) {
console.error('Scan start failed:', error);
}
});
}
3. Connecting to a Device
Once the target device is identified, use its MAC address or UUID to initiate a connection. Immediately after a successful connection, stop the scanning process to free resources.
const targetDeviceId = ref('');
function establishConnection(device) {
targetDeviceId.value = device.deviceId;
uni.createBLEConnection({
deviceId: targetDeviceId.value,
success() {
stopScan();
console.log('Connection established');
},
fail(error) {
console.error('Connection failed:', error);
}
});
}
function stopScan() {
uni.stopBluetoothDevicesDiscovery();
}
4. Discovering Services and Characteristics
BLE devices organize data into Services and Characteristics. You must retrieve these to determine which endpoints support read, write, or notify operations. Typically, hardware documentation provides the specific Service UUID and Characteristic UUID required for communication.
const serviceUuid = ref('YOUR_SERVICE_UUID');
const characteristicUuid = ref('YOUR_CHARACTERISTIC_UUID');
function retrieveServices() {
setTimeout(() => {
uni.getBLEDeviceServices({
deviceId: targetDeviceId.value,
success(res) {
console.log('Services found:', res.services);
retrieveCharacteristics();
}
});
}, 1000); // Delay to ensure platform stability
}
function retrieveCharacteristics() {
uni.getBLEDeviceCharacteristics({
deviceId: targetDeviceId.value,
serviceId: serviceUuid.value,
success(res) {
console.log('Characteristics:', res.characteristics);
}
});
}
5. Listening for Data
To receive data from the peripheral, you must first enable notifications on the characteristic. Then, register a callback for uni.onBLECharacteristicValueChange. Data is received as an ArrayBuffer and must be parsed into a readable string.
const receivedHex = ref('');
const receivedText = ref('');
function enableNotification() {
uni.notifyBLECharacteristicValueChange({
deviceId: targetDeviceId.value,
serviceId: serviceUuid.value,
characteristicId: characteristicUuid.value,
state: true,
success() {
uni.onBLECharacteristicValueChange((res) => {
const hexStr = bufferToHex(res.value);
receivedHex.value = hexStr;
receivedText.value = hexToString(hexStr);
});
}
});
}
function bufferToHex(buffer) {
return Array.from(new Uint8Array(buffer))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
function hexToString(hex) {
let str = '';
for (let i = 0; i < hex.length; i += 2) {
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
}
return str;
}
6. Sending Data
Sending data to a BLE device involves converting the payload into an ArrayBuffer. This is done using uni.writeBLECharacteristicValue. Note that the platform may limit the packet size (usually 20 bytes), requiring large payloads to be chunked.
function writeData(payload) {
const buffer = strToArrayBuffer(payload);
uni.writeBLECharacteristicValue({
deviceId: targetDeviceId.value,
serviceId: serviceUuid.value,
characteristicId: characteristicUuid.value,
value: buffer,
success() {
console.log('Data sent successfully');
},
fail(error) {
console.error('Write failed:', error);
}
});
}
function strToArrayBuffer(str) {
const buffer = new ArrayBuffer(str.length);
const dataView = new DataView(buffer);
for (let i = 0; i < str.length; i++) {
dataView.setUint8(i, str.charCodeAt(i));
}
return buffer;
}
To read data from the device (if the characteristic supports the read property), use uni.readBLECharacteristicValue. This initiates a read request, and the actual data will be delivered via the uni.onBLECharacteristicValueChange listener defined earlier.
function readData() {
uni.readBLECharacteristicValue({
deviceId: targetDeviceId.value,
serviceId: serviceUuid.value,
characteristicId: characteristicUuid.value,
success() {
console.log('Read command issued');
}
});
}
Complete Implementation
<template>
<view>
<scroll-view scroll-y style="height: 300px; border: 1px solid #ccc; margin-bottom: 10px;">
<view v-for="dev in discoveredDevices" @click="establishConnection(dev)" style="padding: 10px; border-bottom: 1px solid #eee;">
{{ dev.name || 'Unknown Device' }} ({{ dev.deviceId }})
</view>
</scroll-view>
<button @click="initializeAdapter">Init Adapter</button>
<button @click="startScan">Start Scan</button>
<button @click="retrieveServices">Get Services</button>
<button @click="enableNotification">Notify</button>
<button @click="writeData('Test')">Write</button>
<button @click="readData">Read</button>
<view style="margin-top: 20px; padding: 10px; background: #f0f0f0;">
<text>Log: {{ receivedText }}</text>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
const discoveredDevices = ref([]);
const targetDeviceId = ref('');
const serviceUuid = ref('YOUR_SERVICE_UUID');
const characteristicUuid = ref('YOUR_CHARACTERISTIC_UUID');
const receivedText = ref('');
function initializeAdapter() {
uni.openBluetoothAdapter({
success: () => console.log('Init OK'),
fail: (e) => console.error(e)
});
}
function startScan() {
uni.startBluetoothDevicesDiscovery({
success: () => {
uni.onBluetoothDeviceFound((res) => {
res.devices.forEach(d => {
if (!discoveredDevices.value.find(x => x.deviceId === d.deviceId)) {
discoveredDevices.value.push(d);
}
});
});
}
});
}
function establishConnection(device) {
targetDeviceId.value = device.deviceId;
uni.createBLEConnection({
deviceId: targetDeviceId.value,
success: () => {
uni.stopBluetoothDevicesDiscovery();
}
});
}
function retrieveServices() {
uni.getBLEDeviceServices({
deviceId: targetDeviceId.value,
success: () => {
// Logic to confirm service ID
}
});
}
function enableNotification() {
uni.notifyBLECharacteristicValueChange({
deviceId: targetDeviceId.value,
serviceId: serviceUuid.value,
characteristicId: characteristicUuid.value,
state: true,
success: () => {
uni.onBLECharacteristicValueChange((res) => {
receivedText.value = hexToString(bufferToHex(res.value));
});
}
});
}
function writeData(str) {
const buffer = new ArrayBuffer(str.length);
const view = new DataView(buffer);
for (let i = 0; i < str.length; i++) {
view.setUint8(i, str.charCodeAt(i));
}
uni.writeBLECharacteristicValue({
deviceId: targetDeviceId.value,
serviceId: serviceUuid.value,
characteristicId: characteristicUuid.value,
value: buffer
});
}
function readData() {
uni.readBLECharacteristicValue({
deviceId: targetDeviceId.value,
serviceId: serviceUuid.value,
characteristicId: characteristicUuid.value
});
}
// Utilities
function bufferToHex(buffer) {
return Array.from(new Uint8Array(buffer)).map(b => b.toString(16).padStart(2, '0')).join('');
}
function hexToString(hex) {
let str = '';
for (let i = 0; i < hex.length; i += 2) str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
return str;
}
</script>