鸿蒙 蓝牙开发之串口通信(四)
本文介绍了蓝牙SPP(串口通信协议)技术及其实现方法。SPP作为蓝牙基础协议,通过模拟串口通信实现设备间可靠数据传输。文章详细说明了客户端和服务端的开发流程:客户端需发起连接(需匹配服务端UUID)、实现数据传输(发送/接收)和断开连接;服务端需创建套接字、监听连接请求并进行数据交互。开发需注意权限申请、异步通信处理及UUID一致性。SPP适用于设备控制、数据同步、文件传输等多种场景,具有广泛的蓝
本文同步发表于微信公众号,微信搜索 程语新视界 即可关注,每个工作日都有文章更新
一、SPP技术
SPP(Serial Port Profile,串口通信协议)是蓝牙技术中的一种基础通信协议,它模拟传统串行端口的通信方式,为设备间提供了可靠的数据传输通道。SPP协议广泛应用于需要双向数据传输的场景。
常见 Profile 及功能(通信协议):
| Profile 类型 | 作用描述 | 开发接口模块 |
|---|---|---|
| A2DP | 高质量音频传输(如耳机播放音乐) | import { a2dp } from '@kit.ConnectivityKit' |
| HFP | 通话控制(接听/挂断电话) | import { hfp } from '@kit.ConnectivityKit' |
| HID | 输入设备交互(如键盘、鼠标) | import { hid } from '@kit.ConnectivityKit' |
| SPP | 串口数据传输(自定义设备通信) | 需手动实现通信协议 |
说明:蓝牙 Profile 能力(核心场景)作用:定义蓝牙设备间的通信协议,实现特定功能交互。
通信模型
在SPP通信交互中,设备根据功能角色不同分为:
-
客户端(Client):主动发起连接请求的一方
-
服务端(Server):提供特定UUID服务,等待客户端连接的一方
实现原理
-
客户端:
-
通过查找设备流程获取服务端的设备地址
-
向服务端特定的UUID发起连接请求
-
连接成功后即可进行双向数据传输
-
-
服务端:
-
创建并注册特定的UUID服务
-
监听客户端的连接请求
-
连接成功后即可进行双向数据传输
-
二、准备工作
2.1 权限申请
必须权限:ohos.permission.ACCESS_BLUETOOTH
-
配置方式:
-
在配置文件中声明权限
-
运行时向用户申请授权
-
2.2 API模块导入
import { socket } from '@kit.ConnectivityKit';
import { BusinessError } from '@kit.BasicServicesKit';
三、客户端实现
3.1 发起连接
3.1.1 连接前提
-
设备发现:通过查找设备流程搜索到目标设备
-
UUID匹配:客户端连接的UUID必须与服务端创建socket时构造的UUID一致
-
服务验证:蓝牙子系统会查询服务端是否支持该UUID服务,不支持则连接失败
3.1.2 连接参数配置
// 设备地址通过查找设备流程获取
let peerDevice = 'XX:XX:XX:XX:XX:XX';
// 定义客户端socket id(初始值为-1表示未连接)
let clientNumber = -1;
// 配置连接参数
let option: socket.SppOptions = {
uuid: '00009999-0000-1000-8000-00805F9B34FB', // 需要连接的服务端UUID服务
secure: false, // 安全模式,false表示非安全连接
type: socket.SppType.SPP_RFCOMM // 使用RFCOMM协议
};
console.info('startConnect ' + peerDevice);
3.1.3 发起连接
socket.sppConnect(peerDevice, option, (err, num: number) => {
if (err) {
console.error('startConnect errCode: ' + (err as BusinessError).code +
', errMessage: ' + (err as BusinessError).message);
} else {
console.info('startConnect clientNumber: ' + num);
clientNumber = num; // 保存连接成功后分配的socket id
}
});
3.2 数据传输
3.2.1 发送数据
前提条件:客户端和服务端连接已建立成功
let clientNumber = 1; // 客户端发起连接时获取的socket id
let arrayBuffer = new ArrayBuffer(2); // 创建数据缓冲区
let data = new Uint8Array(arrayBuffer); // 转换为Uint8Array类型
data[0] = 3; // 设置第一个字节
data[1] = 4; // 设置第二个字节
try {
socket.sppWrite(clientNumber, arrayBuffer); // 发送数据
} catch (err) {
console.error('errCode: ' + (err as BusinessError).code +
', errMessage: ' + (err as BusinessError).message);
}
3.2.2 接收数据
实现方式:通过订阅socket.on('sppRead')接口实现数据接收
let clientNumber = 1; // 客户端发起连接时获取的socket id
// 定义接收数据的回调函数
function read(dataBuffer: ArrayBuffer) {
let data = new Uint8Array(dataBuffer);
console.info('client data: ' + JSON.stringify(data));
}
try {
// 发起订阅,指定接收数据的socket和回调函数
socket.on('sppRead', clientNumber, read);
} catch (err) {
console.error('readData errCode: ' + (err as BusinessError).code +
', errMessage: ' + (err as BusinessError).message);
}
3.3 断开连接
断开流程:先取消读取数据的订阅,再断开连接
let clientNumber = 1; // 客户端发起连接时获取的socket id
// 定义接收数据的回调函数
function read(dataBuffer: ArrayBuffer) {
let data = new Uint8Array(dataBuffer);
console.info('client data: ' + JSON.stringify(data));
}
try {
// 取消接收数据订阅
socket.off('sppRead', clientNumber, read);
} catch (err) {
console.error('off sppRead errCode: ' + (err as BusinessError).code +
', errMessage: ' + (err as BusinessError).message);
}
try {
// 从客户端断开连接
socket.sppCloseClientSocket(clientNumber);
} catch (err) {
console.error('errCode: ' + (err as BusinessError).code +
', errMessage: ' + (err as BusinessError).message);
}
四、服务端实现
4.1 创建服务端套接字
4.1.1 功能说明
服务端通过创建套接字的方式,在蓝牙子系统中注册指定的UUID服务。
4.1.2 关键要点
-
UUID名称:无限制,可使用应用名称
-
UUID匹配:只有服务端与客户端的UUID一致时,连接才能成功建立
4.1.3 实现代码
// 定义服务端socket id(初始值为-1表示未创建)
let serverNumber = -1;
// 配置监听参数
let option: socket.SppOptions = {
uuid: '00009999-0000-1000-8000-00805F9B34FB', // 注册的UUID服务
secure: false, // 安全模式
type: socket.SppType.SPP_RFCOMM // 使用RFCOMM协议
};
// 创建服务端监听socket
socket.sppListen("demonstration", option, (err, num: number) => {
if (err) {
console.error('sppListen errCode: ' + (err as BusinessError).code +
', errMessage: ' + (err as BusinessError).message);
} else {
console.info('sppListen serverNumber: ' + num);
serverNumber = num; // 保存创建成功后分配的socket id
}
});
4.2 监听客户端连接
4.2.1 功能说明
创建好服务端套接字后,服务端即可开始监听客户端的连接请求。
4.2.2 实现代码
let serverNumber = 1; // 创建服务端套接字时获取的socket id
// 定义客户端socket id(初始值为-1表示无连接)
let clientNumber = -1;
socket.sppAccept(serverNumber, (err, num: number) => {
if (err) {
console.error('accept errCode: ' + (err as BusinessError).code +
', errMessage: ' + (err as BusinessError).message);
} else {
console.info('accept clientNumber: ' + num);
clientNumber = num; // 保存客户端连接成功后分配的socket id
}
});
4.3 数据传输
4.3.1 发送数据
let clientNumber = 1; // 服务端监听连接时获取的客户端socket id
let arrayBuffer = new ArrayBuffer(2);
let data = new Uint8Array(arrayBuffer);
data[0] = 9;
data[1] = 8;
try {
socket.sppWrite(clientNumber, arrayBuffer);
} catch (err) {
console.error('sppWrite errCode: ' + (err as BusinessError).code +
', errMessage: ' + (err as BusinessError).message);
}
4.3.2 接收数据
let clientNumber = 1; // 服务端监听连接时获取的客户端socket id
// 定义接收数据的回调函数
function read(dataBuffer: ArrayBuffer) {
let data = new Uint8Array(dataBuffer);
console.info('client data: ' + JSON.stringify(data));
}
try {
// 发起订阅
socket.on('sppRead', clientNumber, read);
} catch (err) {
console.error('readData errCode: ' + (err as BusinessError).code +
', errMessage: ' + (err as BusinessError).message);
}
4.4 断开连接
断开流程:先取消读取数据的订阅,再断开连接
let clientNumber = 1; // 服务端监听连接时获取的客户端socket id
// 定义接收数据的回调函数
function read(dataBuffer: ArrayBuffer) {
let data = new Uint8Array(dataBuffer);
console.info('client data: ' + JSON.stringify(data));
}
try {
// 取消订阅
socket.off('sppRead', clientNumber, read);
} catch (err) {
console.error('off sppRead errCode: ' + (err as BusinessError).code +
', errMessage: ' + (err as BusinessError).message);
}
try {
// 从服务端断开连接(与客户端断开方法相同)
socket.sppCloseClientSocket(clientNumber);
} catch (err) {
console.error('errCode: ' + (err as BusinessError).code +
', errMessage: ' + (err as BusinessError).message);
}
4.5 删除服务端套接字
4.5.1 功能说明
当应用不再需要该服务端套接字时,需要主动关闭创建时获取的套接字,蓝牙子系统会删除此前注册的UUID服务。
4.5.2 重要影响
删除套接字后,如果客户端发起连接,将会连接失败。
4.5.3 实现代码
let clientNumber = 1; // 服务端监听连接时获取的客户端socket id
let serverNumber = 1; // 创建服务端套接字时获取的服务端socket id
// 定义接收数据的回调函数
function read(dataBuffer: ArrayBuffer) {
let data = new Uint8Array(dataBuffer);
console.info('client data: ' + JSON.stringify(data));
}
try {
// 取消订阅(先取消数据接收)
socket.off('sppRead', clientNumber, read);
} catch (err) {
console.error('off sppRead errCode: ' + (err as BusinessError).code +
', errMessage: ' + (err as BusinessError).message);
}
try {
// 若应用不再需要此能力,则主动删除
socket.sppCloseServerSocket(serverNumber);
} catch (err) {
console.error('errCode: ' + (err as BusinessError).code +
', errMessage: ' + (err as BusinessError).message);
}
总结
技术要点
-
角色区分:清晰理解客户端和服务端的不同职责
-
UUID匹配:确保客户端和服务端使用相同的UUID
-
异步通信:SPP操作大部分采用异步回调方式
开发流程
-
准备阶段:申请权限、导入模块、设计通信协议
-
实现阶段:
-
客户端:发起连接、发送数据、接收数据、断开连接
-
服务端:创建套接字、监听连接、发送数据、接收数据、断开连接
-
-
测试阶段:验证连接稳定性、数据传输可靠性、错误处理完整性
SPP协议适用于多种蓝牙数据传输场景:
-
设备控制:远程控制蓝牙设备
-
数据同步:设备间数据同步
-
文件传输:小文件传输
-
传感器数据:传感器数据采集和传输
更多推荐




所有评论(0)