Skip to content

郊狼情趣脉冲主机 V3 蓝牙协议

版本区别

V3 协议特点

  • 无需大小端转换(Big-Endian / Little-Endian)。
  • 双通道合一:单条 B0 指令同时携带 A/B 两通道的强度与波形数据。
  • 更精细的控制:支持 25ms 粒度的波形定义。

📡 连接参数

蓝牙广播名称

  • 主机: 47L121000
  • 传感器: 47L120100

服务与特性 UUID

基础 UUID: 0000xxxx-0000-1000-8000-00805f9b34fb (将下表中的 16 位 UUID 替换 xxxx)

功能Service UUIDChar UUID属性说明
指令写入0x180C0x150AWRITE发送 B0/BF 等控制指令
数据通知0x180C0x150BNOTIFY订阅主机回应 (如 B1)
电量读取0x180A0x1500READ/NOTIFY读取剩余电量

⚡ 核心控制指令 (B0)

B0 指令是核心交互指令,每 100ms 发送一次。包含 20 字节,控制强度变化及波形输出。

数据包结构图解

Byte 0Byte 1Byte 2Byte 3Byte 4-11Byte 12-19
HEAD控制位A 强度B 强度A 通道波形B 通道波形
0xB0Seq + Mode0~2000~2004组 (频率+强度)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-
1A 通道强度软上限0~200限制 A 通道最大输出值
2B 通道强度软上限0~200限制 B 通道最大输出值
3-4频率平衡参数 10~255对应 A/B 通道。值越大,低频冲击感越强
5-6频率平衡参数 20~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)
  • 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
        }
    }
}