20241030191200

This commit is contained in:
zhaoyafan 2024-10-30 19:12:24 +08:00
parent ae38969f30
commit fd1605c246
1 changed files with 170 additions and 9 deletions

179
main.py
View File

@ -1,16 +1,19 @@
import os import os
import re import re
import io
import sys import sys
import ctypes import ctypes
import logging import logging
import threading
import time import time
import json
import hashlib import hashlib
import tempfile import tempfile
import warnings import warnings
import subprocess import subprocess
import win32com.client import win32com.client
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QDialog, QLabel, QComboBox, QCheckBox, QLineEdit, QAction, QMenu, QMessageBox, QPushButton, QTableWidget, QVBoxLayout, QHBoxLayout, QTableWidgetItem from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QDialog, QLabel, QComboBox, QCheckBox, QLineEdit, QAction, QMenu, QMessageBox, QPushButton, QTableWidget, QVBoxLayout, QHBoxLayout, QTableWidgetItem
from PyQt5.QtCore import Qt, QCoreApplication, QTimer from PyQt5.QtCore import Qt, QCoreApplication, QPropertyAnimation, QTimer
from PyQt5.QtGui import QFont from PyQt5.QtGui import QFont
@ -257,7 +260,7 @@ class BtSdk:
return self.dll.Btsdk_IsBluetoothReady() return self.dll.Btsdk_IsBluetoothReady()
def enableDeviceDiscovery(self): def enableDeviceDiscovery(self):
device_class, max_dev_num, max_durations = 0X000400, 30, 7 device_class, max_dev_num, max_durations = 0X000400, 30, 10
code = self.dll.Btsdk_StartDeviceDiscovery(device_class, max_dev_num, max_durations) code = self.dll.Btsdk_StartDeviceDiscovery(device_class, max_dev_num, max_durations)
code != 0 and warnings.warn(self.errs[code]) code != 0 and warnings.warn(self.errs[code])
return not code return not code
@ -376,6 +379,18 @@ class BtSdk:
self.dll.Btsdk_DeleteUnpairedDevicesByClass(0) self.dll.Btsdk_DeleteUnpairedDevicesByClass(0)
return True return True
def isBluetoothActive(self):
data = []
enum_handle = self.dll.Btsdk_StartEnumConnection()
while True:
conn = self.dll.Btsdk_EnumConnection(enum_handle, 0)
if (not conn) == 1:
self.dll.Btsdk_EndEnumConnection(enum_handle)
break
data.append(conn)
return True if len(data) > 0 else False
def _fd(f): def _fd(f):
return f.fileno() if hasattr(f, 'fileno') else f return f.fileno() if hasattr(f, 'fileno') else f
@ -473,6 +488,23 @@ else:
return fcntl.flock(_fd(f), flags) == 0 return fcntl.flock(_fd(f), flags) == 0
def getJson(file, data=None):
if os.path.exists(file):
try:
return json.loads(open(file=file, mode='r', encoding='utf-8').read())
except json.decoder.JSONDecodeError:
return data
return data
def putJson(file, data=None):
with open(file=file, mode='w', encoding='utf-8') as f:
flock(f, LOCK_EX)
res = f.write(json.dumps(data, indent=4, ensure_ascii=True))
flock(f, LOCK_UN)
return res
class LoggerFileHandler: class LoggerFileHandler:
def __init__(self, log_file: str, mode: str = 'a', level: str = None, fmt: str = None): def __init__(self, log_file: str, mode: str = 'a', level: str = None, fmt: str = None):
self.log, self.mod = log_file, mode self.log, self.mod = log_file, mode
@ -549,6 +581,22 @@ class Logger:
self.c = self.logger.critical self.c = self.logger.critical
class Setting(dict):
def __init__(self, setting_file: str, setting_default: dict):
self.data_file = os.path.abspath(setting_file)
super().__init__(getJson(self.data_file, setting_default))
def __getitem__(self, item):
if item in self:
return super().__getitem__(item)
else:
return None
def __setitem__(self, key, value):
super().__setitem__(key, value)
putJson(self.data_file, self)
class CustomLineEdit(QLineEdit): class CustomLineEdit(QLineEdit):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -612,11 +660,14 @@ class MainWindow(QMainWindow):
def __init__(self, logger: Logger): def __init__(self, logger: Logger):
super().__init__() super().__init__()
self.app_name = '蓝牙音频连接' self.app_name = '蓝牙音频连接'
self.app_version = ('1.0.3', '20240815', 'zhaoyafan', 'zhaoyafan@foxmail.com', 'https://www.fanscloud.net/') self.app_version = ('1.1.0', '20241030', 'zhaoyafan', 'zhaoyafan@foxmail.com', 'https://www.fanscloud.net/')
self.app_text = {'search': '正在搜索', 'connecting': '连接中...'} self.app_text = {'search': '正在搜索', 'connecting': '连接中...'}
self.logger = logger self.logger = logger
self.toast = ToastNotification()
self.setting = Setting(os.path.abspath(os.path.join(tempfile.gettempdir(), '%s.config' % self.md5(__file__)[:16])), {})
self.bluesoleil = None self.bluesoleil = None
self.currentDevHandle = None self.currentDevHandle = None
self.currentDevRssi = 0
self.setWindowTitle(self.app_name) self.setWindowTitle(self.app_name)
self.setGeometry(0, 0, 600, 400) self.setGeometry(0, 0, 600, 400)
self.statusBar() self.statusBar()
@ -652,6 +703,12 @@ class MainWindow(QMainWindow):
self.edtBtName.setFixedSize(250, 36) self.edtBtName.setFixedSize(250, 36)
m_layout_1.addWidget(self.edtBtName) m_layout_1.addWidget(self.edtBtName)
# 信号强度
self.edtBtRssi = CustomLineEditNoPopup('')
self.edtBtRssi.setReadOnly(True)
self.edtBtRssi.setFixedSize(38, 36)
m_layout_1.addWidget(self.edtBtRssi)
# 断开连接 # 断开连接
self.butDisconnect = CustomPushButton('断开连接') self.butDisconnect = CustomPushButton('断开连接')
self.butDisconnect.setFixedSize(64, 36) self.butDisconnect.setFixedSize(64, 36)
@ -673,7 +730,7 @@ class MainWindow(QMainWindow):
# 地址输入 # 地址输入
self.edtBtAddr = CustomLineEdit('') self.edtBtAddr = CustomLineEdit('')
self.edtBtAddr.setStyleSheet('QLineEdit {font-size: 24px; font-family: \'Microsoft YaHei\'; color: #000000; background-color: #FFFFEE;}') self.edtBtAddr.setStyleSheet('QLineEdit {font-size: 24px; font-family: \'Microsoft YaHei\'; color: #000000; background-color: #FFFFEE;}')
self.edtBtAddr.setFixedSize(249, 36) self.edtBtAddr.setFixedSize(294, 36)
self.edtBtAddr.returnPressed.connect(self.on_connect_bt_address) self.edtBtAddr.returnPressed.connect(self.on_connect_bt_address)
m_layout_3.addWidget(self.edtBtAddr) m_layout_3.addWidget(self.edtBtAddr)
@ -694,6 +751,15 @@ class MainWindow(QMainWindow):
self.show() self.show()
self.init_bluesoleil() self.init_bluesoleil()
self.connect_status_update_timer = QTimer()
self.connect_status_update_timer.timeout.connect(self.connect_status_update)
if (self.bluesoleil.isBluetoothActive() and self.setting['deviceHandle'] > 0) == 1:
self.in_connect_process()
self.currentDevHandle = self.setting['deviceHandle'] or 0
self.currentDevRssi = self.setting['deviceRssi'] or 0
self.connect_action_ui_update()
def init_bluesoleil(self): def init_bluesoleil(self):
try: try:
self.bluesoleil = BtSdk() self.bluesoleil = BtSdk()
@ -724,6 +790,20 @@ class MainWindow(QMainWindow):
if (not _c) == 0: if (not _c) == 0:
QMessageBox.information(self, '关于', "" 'version: %s, build: %s, author: %s, email: %s, site: %s' % (_c[0], _c[1], _c[2], _c[3], '<a href=\'%s\'>%s</a>' % (_c[4], _c[4]))) QMessageBox.information(self, '关于', "" 'version: %s, build: %s, author: %s, email: %s, site: %s' % (_c[0], _c[1], _c[2], _c[3], '<a href=\'%s\'>%s</a>' % (_c[4], _c[4])))
@staticmethod
def md5(input_data):
if isinstance(input_data, bytes):
return hashlib.md5(input_data).hexdigest()
if isinstance(input_data, str):
return hashlib.md5(bytes(input_data, encoding='utf-8')).hexdigest()
md5_object = hashlib.md5()
while True:
data = input_data.read(io.DEFAULT_BUFFER_SIZE)
if data:
md5_object.update(data)
else:
return md5_object.hexdigest()
@staticmethod @staticmethod
def path_expandvars(path): def path_expandvars(path):
resolve = os.path.expandvars(path) resolve = os.path.expandvars(path)
@ -770,6 +850,8 @@ class MainWindow(QMainWindow):
def in_connect_process(self): def in_connect_process(self):
self.edtBtName.setStyleSheet('font-size: 16px; font-family: \'Microsoft YaHei\'; color: #303030; border: 1px solid #808080; font-weight: bold; background-color: #7BD136') self.edtBtName.setStyleSheet('font-size: 16px; font-family: \'Microsoft YaHei\'; color: #303030; border: 1px solid #808080; font-weight: bold; background-color: #7BD136')
self.edtBtName.setText('') self.edtBtName.setText('')
self.edtBtRssi.setStyleSheet('font-size: 16px; font-family: \'Microsoft YaHei\'; color: #303030; border: 1px solid #808080; font-weight: bold; background-color: #7BD136')
self.edtBtRssi.setText('')
self.edtBtAddr.setText('') self.edtBtAddr.setText('')
self.edtBtAddr.setEnabled(False) self.edtBtAddr.setEnabled(False)
self.edtBtAddr.setFocus() self.edtBtAddr.setFocus()
@ -779,6 +861,8 @@ class MainWindow(QMainWindow):
def un_connect_process(self): def un_connect_process(self):
self.edtBtName.setStyleSheet('font-size: 16px; font-family: \'Microsoft YaHei\'; color: #303030; border: 1px solid #808080; font-weight: bold; background-color: #FDD391') self.edtBtName.setStyleSheet('font-size: 16px; font-family: \'Microsoft YaHei\'; color: #303030; border: 1px solid #808080; font-weight: bold; background-color: #FDD391')
self.edtBtName.setText('') self.edtBtName.setText('')
self.edtBtRssi.setStyleSheet('font-size: 16px; font-family: \'Microsoft YaHei\'; color: #303030; border: 1px solid #808080; font-weight: bold; background-color: #F5F5F5')
self.edtBtRssi.setText('')
self.edtBtAddr.setText('') self.edtBtAddr.setText('')
self.edtBtAddr.setEnabled(True) self.edtBtAddr.setEnabled(True)
self.edtBtAddr.setFocus() self.edtBtAddr.setFocus()
@ -786,8 +870,13 @@ class MainWindow(QMainWindow):
self.butManuSearch.setEnabled(True) self.butManuSearch.setEnabled(True)
def on_disconnect(self): def on_disconnect(self):
self.connect_status_update_timer.stop()
self.bluesoleil.cancelDeviceDiscovery() self.bluesoleil.cancelDeviceDiscovery()
self.bluesoleil.removeAllDevices() self.bluesoleil.removeAllDevices()
self.currentDevHandle = 0
self.currentDevRssi = 0
self.setting['deviceHandle'] = 0
self.setting['deviceRssi'] = 0
self.un_connect_process() self.un_connect_process()
def on_autosearch(self): def on_autosearch(self):
@ -826,22 +915,41 @@ class MainWindow(QMainWindow):
return None return None
return self.connect_action_ui(text) return self.connect_action_ui(text)
def connect_action_ui_update(self):
self.connect_status_update_timer.start(1500)
self.edtBtAddr.setText(str(self.bluesoleil.getDeviceAddr(self.currentDevHandle)).replace(':', ''))
self.edtBtName.setText(str(self.bluesoleil.getDeviceName(self.currentDevHandle)))
self.edtBtRssi.setText(str(self.currentDevRssi if self.currentDevRssi != -3 else ''))
if -3 == self.currentDevRssi:
self.edtBtRssi.setStyleSheet('font-size: 16px; font-family: \'Microsoft YaHei\'; color: #303030; border: 1px solid #808080; font-weight: bold; background-color: #FFF3F3')
if -65 < self.currentDevRssi <= -5:
self.edtBtRssi.setStyleSheet('font-size: 16px; font-family: \'Microsoft YaHei\'; color: #303030; border: 1px solid #808080; font-weight: bold; background-color: #73CD2A')
if -72 < self.currentDevRssi <= -65:
self.edtBtRssi.setStyleSheet('font-size: 16px; font-family: \'Microsoft YaHei\'; color: #303030; border: 1px solid #808080; font-weight: bold; background-color: #F2B32D')
if -99 < self.currentDevRssi <= -72:
self.edtBtRssi.setStyleSheet('font-size: 16px; font-family: \'Microsoft YaHei\'; color: #303030; border: 1px solid #808080; font-weight: bold; background-color: #F25555')
def connect_action_ui(self, handle): def connect_action_ui(self, handle):
if (handle != 0) == 1: if (handle != 0) == 1:
self.in_connect_process() self.in_connect_process()
self.setWindowTitle(self.app_text['connecting']) self.setWindowTitle(self.app_text['connecting'])
if (not self.connect(handle)) == 1: self.currentDevRssi = self.connect(handle) or 0
else:
self.currentDevRssi = 0
if (not self.currentDevRssi) == 1:
self.un_connect_process() self.un_connect_process()
self.edtBtAddr.selectAll() self.edtBtAddr.selectAll()
self.setWindowTitle(self.app_name) self.setWindowTitle(self.app_name)
QMessageBox.critical(self, '提示', '连接失败') QMessageBox.critical(self, '提示', '连接失败')
return None return None
else: else:
self.edtBtAddr.setText(str(self.bluesoleil.getDeviceAddr(self.currentDevHandle)).replace(':', '')) self.connect_action_ui_update()
self.edtBtName.setText(str(self.bluesoleil.getDeviceName(self.currentDevHandle)))
self.setWindowTitle(self.app_name) self.setWindowTitle(self.app_name)
return True return True
def connect_status_update(self):
self.bluesoleil.isBluetoothActive() or self.on_disconnect()
def connect(self, data): def connect(self, data):
devHandle = data devHandle = data
if (isinstance(data, str)) == 1: if (isinstance(data, str)) == 1:
@ -853,11 +961,23 @@ class MainWindow(QMainWindow):
break break
if (not devHandle) == 1: if (not devHandle) == 1:
return None return None
rssi = 0
for i in range(8):
time.sleep(0.25)
rssi = self.bluesoleil.getDeviceRSSI(devHandle)
if (rssi != 0) == 1:
break
code = self.bluesoleil.connectAudioService(devHandle) code = self.bluesoleil.connectAudioService(devHandle)
if (not code) == 1: if (not code) == 1:
return None return None
self.currentDevHandle = devHandle self.currentDevHandle = devHandle
return True self.setting['deviceHandle'] = devHandle
self.setting['deviceRssi'] = rssi
if (not rssi and code > 0) == 1:
return -3
else:
return rssi
class ManuWindow(QDialog): class ManuWindow(QDialog):
def __init__(self, mainwindow): def __init__(self, mainwindow):
@ -938,10 +1058,11 @@ class ManuWindow(QDialog):
self.mainwindow.bluesoleil.enableDeviceDiscovery() self.mainwindow.bluesoleil.enableDeviceDiscovery()
except AttributeError: except AttributeError:
self.close() self.close()
self.mainwindow.toast.show_toast('正在搜索设备')
timer = QTimer(self) timer = QTimer(self)
timer.timeout.connect(self.task_end_search) timer.timeout.connect(self.task_end_search)
timer.setSingleShot(True) timer.setSingleShot(True)
timer.start(7000) timer.start(9999)
def on_connect(self, data): def on_connect(self, data):
self.mainwindow.bluesoleil.cancelDeviceDiscovery() self.mainwindow.bluesoleil.cancelDeviceDiscovery()
@ -956,6 +1077,46 @@ class ManuWindow(QDialog):
pass pass
class ToastNotification(QDialog):
def __init__(self):
super().__init__()
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.WindowType.Tool)
layout = QVBoxLayout()
layout.setContentsMargins(5, 705, 5, 5)
self.label = QLabel("")
self.label.setStyleSheet("font-family: 'Microsoft YaHei'; font-size: 24px; color: #ffffff;")
layout.addWidget(self.label)
self.setLayout(layout)
self.setAttribute(Qt.WA_TranslucentBackground)
self.setStyleSheet("background-color: rgba(65, 65, 65, 180); border-radius: 5px; padding: 10px 16px 10px 16px")
self.adjustSize()
self.timer = QTimer(self)
self.timer.setInterval(2000)
self.timer.timeout.connect(self.close)
self.fade_animation = QPropertyAnimation(self, b"windowOpacity")
self.fade_animation.setDuration(500)
def show_toast(self, message):
self.close()
self.label.setText(message)
self.timer.isActive() and self.timer.stop()
self.timer.start()
self.adjustSize()
self.fade_animation.setStartValue(0.0)
self.fade_animation.setEndValue(1.0)
self.show()
self.fade_animation.start()
def closeEvent(self, event):
if self.timer.isActive():
self.timer.stop()
super().closeEvent(event)
if __name__ == '__main__': if __name__ == '__main__':
if (os.path.basename(__file__).lower().endswith('.int')) == 1: if (os.path.basename(__file__).lower().endswith('.int')) == 1:
QCoreApplication.addLibraryPath(os.path.abspath(os.path.join(os.path.dirname(__file__), 'site-packages/PyQt5/Qt5/plugins'))) QCoreApplication.addLibraryPath(os.path.abspath(os.path.join(os.path.dirname(__file__), 'site-packages/PyQt5/Qt5/plugins')))