小米耳机协议分析--实现电脑端优雅切换降噪/通透

        咱就是说,买了红米的Redmi Buds 5,手机端需要下一个叫做小米耳机的软件,电脑端就不知道如何操作,每次连到电脑,都要用手去按耳机切换模式,太不优雅了,于是我决定分析下这个耳机的协议。

1.抓取蓝牙数据包

        我的手机是Realme手机,系统自带了日志抓取工具,这里正好用于抓原始数据包。                     

 抓取过程就不多演示了,连接上耳机,打开手机端小米耳机软件,反复做一些切换模式操作,记录下操作的间隔时间,和相应的效果,以便于和报文对应上。

2.分析数据包

        CFA文件就是我们要的原始数据包,具体提取方式在Realme的反馈工具箱,我的反馈,Bluetooth/btsnoop_hci/CsLogxxxxx里可以找到。

        拿到电脑上之后,使用Wireshark打开。简单的过滤一下手机和耳机的交互。

        可以发现,手机和耳机是通过RFCOMM协议,28通道交互的,具体的报文看起来格式都差不多,和手机端对应操作时间相对应一下,就能发现具体控制命令。

        以切换为降噪模式举例,fedcbac70e000409020401ef,FEDCBA是头,c70e00应该是私有协议,04代表后面09020401的长度,09是一个自增变量,02代表后面0401的长度,04应该是模式切换的功能码,01是降噪,00是关闭,03是通透。EF是尾。所有报文格式都是这套。

        那接下来就要开始尝试了,用Deepseek简单写了个Windows端RFCOMM交互的工具,和耳机尝试收发一些数据,成功切换降噪,通透!

3.总结和代码

        经过很多次测试,耳机也会主动发一些数据包到电脑,戴上,摘下都会发数据包同步,具体也很容易看出来耳机的电量等状态,有兴趣的可以自己分析下,(小米数据包里的自增变量不增也没事,没有校验)。

        具体上位机代码如下,不保证适配所有小米耳机。记得修改MAC地址对应上你的耳机

#!/usr/bin/env python
# -*- coding: gb2312 -*-

import tkinter as tk
from tkinter import messagebox
import socket
import threading

class BluetoothApp:
    def __init__(self, root):
        self.root = root
        self.root.title("蓝牙控制面板")
        
        # 蓝牙连接状态
        self.connected = False
        self.sock = None
        
        # 设备配置
        self.device_address = "9C:41:12:11:11:1E"  # 请修改为你的设备地址
        self.port = 28  
        # 创建UI
        self.create_widgets()
        
        # 自动连接
        self.connect_bluetooth()

    def create_widgets(self):
        # 连接状态显示
        self.status_label = tk.Label(self.root, text="状态: 未连接", fg="red")
        self.status_label.pack(pady=5)
        
        # 按钮1
        self.btn1 = tk.Button(
            self.root, 
            text="发送命令1 (FE DC BA C4 08 00 04 0F 02 04 00 EF)",
            command=lambda: self.send_hex("FE DC BA C4 08 00 04 0F 02 04 00 EF"),
            state=tk.DISABLED
        )
        self.btn1.pack(pady=5, padx=20, fill=tk.X)
        
        # 按钮2
        self.btn2 = tk.Button(
            self.root, 
            text="发送命令2 (FE DC BA C4 08 00 04 0F 02 04 01 EF)",
            command=lambda: self.send_hex("FE DC BA C4 08 00 04 0F 02 04 01 EF"),
            state=tk.DISABLED
        )
        self.btn2.pack(pady=5, padx=20, fill=tk.X)
        
        # 按钮3
        self.btn3 = tk.Button(
            self.root, 
            text="发送命令3 (FE DC BA C4 08 00 04 0F 02 04 02 EF)",
            command=lambda: self.send_hex("FE DC BA C4 08 00 04 0F 02 04 02 EF"),
            state=tk.DISABLED
        )
        self.btn3.pack(pady=5, padx=20, fill=tk.X)
        
        # 接收框
        self.receive_text = tk.Text(self.root, height=10, state=tk.DISABLED)
        self.receive_text.pack(pady=10, padx=20, fill=tk.BOTH, expand=True)
        
        # 清空按钮
        self.clear_btn = tk.Button(
            self.root, 
            text="清空接收区",
            command=self.clear_receive
        )
        self.clear_btn.pack(pady=5)
        
        # 退出按钮
        self.exit_btn = tk.Button(
            self.root, 
            text="退出",
            command=self.on_close
        )
        self.exit_btn.pack(pady=5)

    def connect_bluetooth(self):
        try:
            self.sock = socket.socket(socket.AF_BLUETOOTH, 
                                    socket.SOCK_STREAM, 
                                    socket.BTPROTO_RFCOMM)
            self.sock.connect((self.device_address, self.port))
            self.connected = True
            self.status_label.config(text="状态: 已连接", fg="green")
            
            # 启用按钮
            self.btn1.config(state=tk.NORMAL)
            self.btn2.config(state=tk.NORMAL)
            self.btn3.config(state=tk.NORMAL)
            
            # 启动接收线程
            self.receiving = True
            receive_thread = threading.Thread(target=self.receive_data)
            receive_thread.daemon = True
            receive_thread.start()
            
        except Exception as e:
            messagebox.showerror("连接错误", f"无法连接蓝牙设备: {e}")

    def send_hex(self, hex_str):
        if not self.connected:
            messagebox.showwarning("未连接", "蓝牙未连接,无法发送数据")
            return
            
        try:
            bytes_to_send = bytes.fromhex(hex_str.replace(" ", ""))
            self.sock.send(bytes_to_send)
            self.append_receive(f"[发送] {bytes_to_send.hex(' ').upper()}")
        except Exception as e:
            messagebox.showerror("发送错误", f"发送数据失败: {e}")

    def receive_data(self):
        while self.receiving:
            try:
                self.sock.settimeout(0.5)
                data = self.sock.recv(1024)
                if data:
                    hex_data = data.hex(' ').upper()
                    self.append_receive(f"[接收] {hex_data}")
            except socket.timeout:
                continue
            except Exception as e:
                if self.receiving:  # 避免关闭时的错误提示
                    self.append_receive(f"[错误] 接收数据出错: {e}")
                break

    def append_receive(self, text):
        self.receive_text.config(state=tk.NORMAL)
        self.receive_text.insert(tk.END, text + "\n")
        self.receive_text.see(tk.END)
        self.receive_text.config(state=tk.DISABLED)

    def clear_receive(self):
        self.receive_text.config(state=tk.NORMAL)
        self.receive_text.delete(1.0, tk.END)
        self.receive_text.config(state=tk.DISABLED)

    def on_close(self):
        self.receiving = False
        if self.sock:
            try:
                self.sock.close()
            except:
                pass
        self.root.destroy()

if __name__ == "__main__":
    root = tk.Tk()
    app = BluetoothApp(root)
    root.protocol("WM_DELETE_WINDOW", app.on_close)
    root.mainloop()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值