用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软件
运行示例
打包成windows下exe文件
搜不到蓝牙,还不知道为什么
如果,感到此时的自己很辛苦,那告诉自己:容易走的都是下坡路。坚持住,因为你正在走上坡路,走过去,你就一定会有进步。如果,你正在埋怨命运不眷顾,开导自己:命,是失败者的借口;运,是成功者的谦词。命运从来都是掌握在自己的手中,埋怨,只是一种懦弱的表现;努力,才是人生的态度。