用hc-05d模块实现usb转ttl模块功能

hc-05d模块特点
hc-05d是支持spp和ble,固件是主从一体的,默认情况下进入透传模式
默认波特率是9600

bleak和pybluez是Python中两个不同的蓝牙通信库
pybluez:仅支持经典蓝牙(BR/EDR),如蓝牙2.x/3.x/4.x
bleak:专为低功耗蓝牙(BLE/Bluetooth 4.0+)设计


目标板波特率是115200,故将hc05d模块波特率改为115200

用python实现的
代码

点击查看代码
import sys
import asyncio
from bleak import BleakScanner, BleakClient
from PyQt5.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QHBoxLayout,
                            QPushButton, QListWidget, QTextEdit, QLabel, 
                            QWidget, QLineEdit, QComboBox, QFrame)
from PyQt5.QtCore import QTimer
from PyQt5.QtGui import QFont

class BLEClientApp(QMainWindow):
    def __init__(self):
        super().__init__()
        self.client = None
        self.write_char = None
        self.setWindowTitle("BLE Client")
        self.setGeometry(100, 100, 800, 600)
        
        self.setup_ui()
        
    def setup_ui(self):
        central_widget = QWidget()
        main_layout = QVBoxLayout()
        
        # 控制面板
        control_panel = QHBoxLayout()
        self.scan_btn = QPushButton("Scan")
        self.connect_btn = QPushButton("Connect")
        self.disconnect_btn = QPushButton("Disconnect")
        control_panel.addWidget(self.scan_btn)
        control_panel.addWidget(self.connect_btn)
        control_panel.addWidget(self.disconnect_btn)
        
        # 发送面板
        send_panel = QHBoxLayout()
        self.send_input = QLineEdit()
        self.encoding_combo = QComboBox()
        self.encoding_combo.addItems(["utf-8", "ascii", "latin1"])
        self.send_btn = QPushButton("Send")
        send_panel.addWidget(self.send_input, 4)
        send_panel.addWidget(self.encoding_combo, 1)
        send_panel.addWidget(self.send_btn, 1)
        
        # 设备列表和数据展示
        self.device_list = QListWidget()
        
        # 接收数据区域
        received_data_layout = QVBoxLayout()
        received_data_layout.addWidget(QLabel("Received Data:"))
        
        self.data_display = QTextEdit()
        self.data_display.setReadOnly(True)
        
        # 创建清除按钮区域
        clear_button_layout = QHBoxLayout()
        clear_button_layout.addStretch()  # 添加弹性空间使按钮靠右
        self.clear_btn = QPushButton("Clear")
        self.clear_btn.setFixedWidth(80)  # 设置固定宽度
        clear_button_layout.addWidget(self.clear_btn)
        
        # 将数据展示区域和清除按钮添加到接收数据布局
        received_data_layout.addWidget(self.data_display)
        received_data_layout.addLayout(clear_button_layout)
        
        self.status_label = QLabel("Status: Disconnected")
        
        # 布局组装
        main_layout.addLayout(control_panel)
        main_layout.addWidget(QLabel("Devices:"))
        main_layout.addWidget(self.device_list)
        
        main_layout.addLayout(received_data_layout)

        main_layout.addWidget(QLabel("Send Data:"))
        main_layout.addLayout(send_panel)

        main_layout.addWidget(self.status_label)
        
        central_widget.setLayout(main_layout)
        self.setCentralWidget(central_widget)
        
        # 事件绑定
        self.scan_btn.clicked.connect(self.start_scan)
        self.connect_btn.clicked.connect(self.connect_to_device)
        self.disconnect_btn.clicked.connect(self.disconnect_device)
        self.send_btn.clicked.connect(self.send_data)
        self.clear_btn.clicked.connect(self.clear_data_display)  # 绑定清除按钮
        
        # 异步事件循环
        self.loop = asyncio.get_event_loop()
        self.timer = QTimer()
        self.timer.timeout.connect(self.process_events)
        self.timer.start(100)
    
    def process_events(self):
        self.loop.stop()
        self.loop.run_forever()
    
    def log_message(self, message):
        self.data_display.append(message)
    
    def clear_data_display(self):
        """清空接收数据区域"""
        self.data_display.clear()
    
    def start_scan(self):
        self.device_list.clear()
        self.log_message("Scanning...")
        
        async def scan():
            devices = await BleakScanner.discover()
            for d in devices:
                self.device_list.addItem(f"{d.name or 'Unknown'} - {d.address}")
            self.log_message(f"Found {len(devices)} devices")
        
        asyncio.run_coroutine_threadsafe(scan(), self.loop)
    
    def connect_to_device(self):
        selected = self.device_list.currentItem()
        if not selected:
            self.log_message("No device selected")
            return
            
        device_address = selected.text().split(" - ")[-1]
        self.log_message(f"Connecting to {device_address}...")
        
        async def connect():
            try:
                self.client = BleakClient(device_address)
                await self.client.connect()
                self.status_label.setText(f"Connected: {device_address}")
                
                # 查找特征
                for service in self.client.services:
                    for char in service.characteristics:
                        if "write" in char.properties:
                            self.write_char = char
                            max_length = char.max_write_without_response if hasattr(char, 'max_write_without_response') else None
                            self.log_message(f"Writable: {char.uuid} (Max length: {max_length})")
                        if "notify" in char.properties:
                            await self.client.start_notify(
                                char.uuid,
                                lambda s, d: self.log_message(
                                    f"{bytes(d).decode('utf-8', errors='replace')}"
                                )
                            )
                self.log_message(f"Characteristic properties: {char.properties}")
                self.log_message(f"Descriptors: {char.descriptors}")
            except Exception as e:
                self.log_message(f"Connection error: {str(e)}")
        
        asyncio.run_coroutine_threadsafe(connect(), self.loop)
    
    def disconnect_device(self):
        if not self.client:
            return
            
        async def disconnect():
            await self.client.disconnect()
            self.status_label.setText("Disconnected")
            self.client = None
            self.write_char = None
        
        asyncio.run_coroutine_threadsafe(disconnect(), self.loop)
    
    def send_data(self):
        if not self.client or not self.write_char:
            self.log_message("Not ready for sending")
            return
            
        text = self.send_input.text()
        if not text:
            return
            
        encoding = self.encoding_combo.currentText()
        
        async def send():
            try:
                data = text.encode(encoding, errors='replace')
                
                # 获取特征值的最大写入长度
                max_length = self.write_char.max_write_without_response if hasattr(self.write_char, 'max_write_without_response') else 20
                if max_length is None:
                    max_length = 20  # 默认值
                    
                self.log_message(f"Data length: {len(data)}, Max write length: {max_length}")
                
                # 如果数据长度小于等于最大长度,直接发送
                if len(data) <= max_length:
                    await self.client.write_gatt_char(self.write_char.uuid, data)
                    self.log_message(f"TX: {text} ({encoding})")
                else:
                    # 分块发送数据
                    for i in range(0, len(data), max_length):
                        chunk = data[i:i+max_length]
                        await self.client.write_gatt_char(self.write_char.uuid, chunk)
                        self.log_message(f"Sent chunk {i//max_length + 1}: {chunk.hex()}")
                        
                    self.log_message(f"TX: {text} ({encoding}) [Sent in {((len(data)-1)//max_length)+1} chunks]")
            except Exception as e:
                self.log_message(f"Send error: {str(e)}")
                #self.log_message("Send")
        
        asyncio.run_coroutine_threadsafe(send(), self.loop)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = BLEClientApp()
    window.show()
    sys.exit(app.exec_())

vs code软件
image

运行示例
image

打包成windows下exe文件
搜不到蓝牙,还不知道为什么
image

posted @ 2025-08-30 10:27  我爱茜茜公主  阅读(1)  评论(0)    收藏  举报