郊狼情趣脉冲主机 V3 蓝牙协议
版本区别
V3 协议特点:
- 无需大小端转换(Big-Endian / Little-Endian)。
- 双通道合一:单条
B0指令同时携带 A/B 两通道的强度与波形数据。 - 更精细的控制:支持 25ms 粒度的波形定义。
📡 连接参数
蓝牙广播名称
- 主机:
47L121000 - 传感器:
47L120100
服务与特性 UUID
基础 UUID:
0000xxxx-0000-1000-8000-00805f9b34fb(将下表中的 16 位 UUID 替换 xxxx)
| 功能 | Service UUID | Char UUID | 属性 | 说明 |
|---|---|---|---|---|
| 指令写入 | 0x180C | 0x150A | WRITE | 发送 B0/BF 等控制指令 |
| 数据通知 | 0x180C | 0x150B | NOTIFY | 订阅主机回应 (如 B1) |
| 电量读取 | 0x180A | 0x1500 | READ/NOTIFY | 读取剩余电量 |
⚡ 核心控制指令 (B0)
B0 指令是核心交互指令,每 100ms 发送一次。包含 20 字节,控制强度变化及波形输出。
数据包结构图解
| Byte 0 | Byte 1 | Byte 2 | Byte 3 | Byte 4-11 | Byte 12-19 |
|---|---|---|---|---|---|
| HEAD | 控制位 | A 强度 | B 强度 | A 通道波形 | B 通道波形 |
0xB0 | Seq + Mode | 0~200 | 0~200 | 4组 (频率+强度) | 4组 (频率+强度) |
1. 控制位详解 (Byte 1)
Byte 1 由 序列号 (Seq) 和 强度模式 (Mode) 组成,各占 4 bits。
c
Byte_1 = (Sequence_Number << 4) | Strength_Mode;序列号 (High 4 bits)
- 范围:
0b0000~0b1111(0-15) - 作用: 用于确认强度修改是否生效。
- 机制:
- 若不需要主机反馈强度:设为
0。 - 若修改了强度:设为
>0的值(如自增)。主机将通过0x150B特性返回一条B1指令,携带相同的序列号以确认修改成功。
- 若不需要主机反馈强度:设为
竞态条件防范
当通过 B0 修改强度且序列号不为 0 时,建议阻塞后续强度修改,直到收到 0x150B 返回相同序列号的 B1 确认包。
强度模式 (Low 4 bits)
低 4 位分为两部分:High 2 bits 控制 A 通道,Low 2 bits 控制 B 通道。
| 二进制值 | 模式名称 | 行为逻辑 | 示例 (当前=10, 设定=5) |
|---|---|---|---|
00 | 无效 | 忽略设定值,保持当前强度 | 结果=10 |
01 | 相对增加 | 当前值 + 设定值 | 结果=15 |
10 | 相对减少 | 当前值 - 设定值 | 结果=5 |
11 | 绝对赋值 | 直接变为设定值 | 结果=5 |
2. 波形数据详解 (Byte 4-19)
每个通道包含 4 组数据(每组对应 25ms,合计 100ms)。
- A 通道: Byte 4-7 (频率), Byte 8-11 (强度)
- B 通道: Byte 12-15 (频率), Byte 16-19 (强度)
数据范围与映射
- 波形强度:
0~100(超过 100 视为无效数据,该通道本周期不输出) - 波形频率:
10~240(对应物理频率 10Hz~1000Hz)
📐 频率映射算法 (Kotlin 参考)
将 10~1000 的输入值映射到协议支持的 10~240 范围:
kotlin
fun mapFrequency(input: Int): Int {
return when (input) {
in 10..100 -> input
in 101..600 -> (input - 100) / 5 + 100
in 601..1000 -> (input - 600) / 10 + 200
else -> 10 // 默认值
}
}🛡️ 全局配置指令 (BF)
用于设置安全限制和体感参数。
⚠️ 必读:初始化重置
BF 指令没有返回值,且修改后直接生效并断电保存。 每次设备连接后,必须重新写入一次 BF 指令以确保软上限符合预期,防止意外的高强度伤害。
数据包结构 (7 Bytes)
0xBF + A上限 + B上限 + 频率参数1(A/B) + 频率参数2(A/B)
| 字节位置 | 含义 | 范围 | 说明 |
|---|---|---|---|
| 0 | 指令头 | 0xBF | - |
| 1 | A 通道强度软上限 | 0~200 | 限制 A 通道最大输出值 |
| 2 | B 通道强度软上限 | 0~200 | 限制 B 通道最大输出值 |
| 3-4 | 频率平衡参数 1 | 0~255 | 对应 A/B 通道。值越大,低频冲击感越强 |
| 5-6 | 频率平衡参数 2 | 0~255 | 对应 A/B 通道。值越大,低频刺激感越强 |
🔔 主机回应消息
请订阅特性 0x150B 以获取通知。
B1 强度反馈
当强度发生变化(无论是通过 APP 修改还是实体旋钮修改)时触发。
结构: 0xB1 (1 Byte) + 序列号 (1 Byte) + A 实际强度 (1 Byte) + B 实际强度 (1 Byte)
- 如果是由 APP 发送
B0触发,序列号与发送时一致。 - 如果是由实体旋钮触发,
序列号通常为0。
📝 开发示例
场景一:仅输出波形,不改强度
假设只想让 A 通道输出波形,B 通道静默。
- Header:
0xB0 - Control:
0x00(Seq=0, Mode=0000) - Target Strength:
0x00,0x00(忽略) - Waveform A: 频率
{10,10,20,30}, 强度{0,10,20,30} - Waveform B: 设一个大于 100 的强度值使其无效。
HEX 示例:
B0 00 00 00 00 0A 0A 14 1E 00 0A 14 1E 00 00 00 00 00 00 00 65(注: 结尾的 0x65 = 101,导致 B 通道数据被丢弃)
场景二:增加强度 + 输出波形
将 A 通道强度绝对值设为 25,并要求反馈。
- Header:
0xB0 - Control:
0x17(Seq=1, Mode=0111 -> A绝对值(11), B无效(11不影响低位? 需注意位操作))- 修正: Mode=0b1100 (A绝对/B无效) ->
0x1C - 假设我们要 A 相对增加 10: Mode=
0b0100-> Byte1=0x14(Seq=1)
- 修正: Mode=0b1100 (A绝对/B无效) ->
- Target Strength: A=
10, B=0
HEX 示例 (A+10):
B0 14 0A 00 ...波形数据...主机回应 (Notify):
B1 01 19 0A ... (假设之前A是15, 15+10=25=0x19)推荐代码逻辑流 (伪代码)
点击展开完整交互逻辑
kotlin
// 状态变量
var isInputAllowed = true // 是否允许发送新的强度命令
var currentSeq = 0 // 当前序列号
// 1. 准备发送 B0 数据
fun sendPulseData(targetStrengthA: Int, modeA: Int) {
if (!isInputAllowed && modeA != 0) {
// 如果正在等待上一条强度命令的回调,建议跳过本次强度修改,仅发送波形
// 或者缓存请求
return
}
var byte1 = 0
if (modeA != 0) {
currentSeq = (currentSeq + 1) % 16
if (currentSeq == 0) currentSeq = 1 // 避免为0
byte1 = (currentSeq << 4) | (modeA << 2)
isInputAllowed = false // 锁住,等待回调
}
// 发送 BLE 指令 0xB0 + byte1 + ...
}
// 2. 处理 0x150B 通知
fun onNotify(data: ByteArray) {
if (data[0] == 0xB1) {
val returnSeq = data[1]
val realStrengthA = data[2]
val realStrengthB = data[3]
// 更新本地显示的强度UI
updateUI(realStrengthA, realStrengthB)
// 解锁
if (returnSeq == currentSeq) {
isInputAllowed = true
}
}
}