car_ui/screens/setting/bluetooth_screen.py
2026-03-31 23:54:39 +03:00

192 lines
7.6 KiB
Python

from __future__ import annotations
from PySide6.QtCore import Qt, QTimer, QSettings, QEvent
from PySide6.QtGui import QFont
from PySide6.QtWidgets import (
QWidget,
QLabel,
QVBoxLayout,
QHBoxLayout,
QPushButton,
QListWidget,
QListWidgetItem,
QScroller,
)
import build_info
from services.bluetooth_service import BluetoothService, BluetoothDevice
class BluetoothScreen(QWidget):
def __init__(self, on_back):
super().__init__()
self._on_back = on_back
self._settings = QSettings("car_ui", "ui")
self._bt_service = BluetoothService(self)
self._discoverable_timer = QTimer(self)
self._discoverable_timer.timeout.connect(self._refresh_discoverable)
root = QVBoxLayout(self)
root.setContentsMargins(0, 0, 0, 0)
root.setSpacing(12)
self.status = QLabel("Статус: —")
self.status.setObjectName("BluetoothStatus")
self.status.setFont(QFont("", 12))
self.list = QListWidget()
self.list.setObjectName("BluetoothList")
self.list.setSpacing(6)
self.list.setSelectionMode(QListWidget.SingleSelection)
self.list.itemSelectionChanged.connect(self._on_select)
QScroller.scroller(self.list.viewport()).grabGesture(
self.list.viewport(),
QScroller.LeftMouseButtonGesture,
)
actions = QHBoxLayout()
actions.setContentsMargins(0, 0, 0, 0)
actions.setSpacing(12)
# Кнопка скрыта, видимость включается автоматически при входе в меню
self.btn_visible = QPushButton("Сделать видимым")
self.btn_visible.setObjectName("BluetoothActionBtn")
self.btn_visible.setMinimumHeight(56)
self.btn_visible.clicked.connect(self._make_visible)
self.btn_visible.setVisible(False)
self.btn_refresh = QPushButton("Обновить список")
self.btn_refresh.setObjectName("BluetoothActionBtn")
self.btn_refresh.setMinimumHeight(56)
self.btn_refresh.clicked.connect(self.refresh_list)
self.btn_refresh.setVisible(False) # скрываем, обновляемся автоматически
self.btn_connect = QPushButton("Подключить")
self.btn_connect.setObjectName("BluetoothActionBtnPrimary")
self.btn_connect.setMinimumHeight(56)
self.btn_connect.setEnabled(False)
self.btn_connect.clicked.connect(self._connect_selected)
self.btn_disconnect = QPushButton("Отключить")
self.btn_disconnect.setObjectName("BluetoothActionBtn")
self.btn_disconnect.setMinimumHeight(56)
self.btn_disconnect.setEnabled(False)
self.btn_disconnect.clicked.connect(self._disconnect_selected)
actions.addWidget(self.btn_visible, 1)
actions.addWidget(self.btn_refresh, 1)
actions.addWidget(self.btn_connect, 1)
actions.addWidget(self.btn_disconnect, 1)
root.addWidget(self.status)
root.addWidget(self.list, 1)
root.addLayout(actions)
# Подписка на событие успешного сопряжения
self._bt_service.pairing_completed.connect(self.refresh_list)
self.refresh_list()
QTimer.singleShot(200, self._auto_connect_last)
def refresh_list(self):
"""Обновить список сопряженных устройств."""
devices = self._bt_service.get_paired_devices()
self.list.clear()
for dev in devices:
label = f"{dev.name} ({dev.mac})" if dev.name else dev.mac
item = QListWidgetItem(label)
item.setData(Qt.UserRole, dev.mac)
self.list.addItem(item)
self._update_status()
def _on_select(self):
"""Обработчик выбора устройства."""
has_selection = self._selected_mac() is not None
self.btn_connect.setEnabled(has_selection)
self.btn_disconnect.setEnabled(has_selection)
self._update_status()
def _selected_mac(self) -> str | None:
"""Получить MAC-адрес выбранного устройства."""
items = self.list.selectedItems()
if not items:
return None
return items[0].data(Qt.UserRole)
def _auto_connect_last(self):
"""Автоподключение к последнему устройству."""
last_mac = self._settings.value("bluetooth/last_mac", "")
if not last_mac:
return
self._connect_device(last_mac, silent=True)
def _make_visible(self):
"""Сделать устройство видимым для сопряжения."""
success = self._bt_service.make_discoverable()
if self._bt_service.last_error == "power_off":
self.status.setText("Статус: питание BT выключено (проверьте rfkill)")
elif self._bt_service.last_error == "max_devices":
self.status.setText(
f"Статус: достигнуто макс. число устройств ({build_info.BLUETOOTH_MAX_PAIRED_DEVICES})"
)
elif not success:
self.status.setText(f"Статус: ошибка ({self._bt_service.last_error})")
else:
self.status.setText("Статус: видим для сопряжения (10 сек)")
self._discoverable_timer.start(9000) # 9 секунд
def _refresh_discoverable(self):
"""Продлить режим сопряжения."""
self._bt_service.make_discoverable()
self._discoverable_timer.start(9000) # 9 секунд
def _connect_selected(self):
"""Подключить выбранное устройство."""
mac = self._selected_mac()
if not mac:
return
self._connect_device(mac, silent=False)
def _connect_device(self, mac: str, silent: bool):
"""Подключиться к устройству по MAC."""
success = self._bt_service.connect(mac)
if not silent:
if not success:
self.status.setText(f"Статус: ошибка ({self._bt_service.last_error})")
else:
self.status.setText(f"Статус: подключаемся к {mac}")
self._settings.setValue("bluetooth/last_mac", mac)
QTimer.singleShot(300, self._update_status)
def _disconnect_selected(self):
"""Отключить выбранное устройство."""
mac = self._selected_mac()
if not mac:
return
success = self._bt_service.disconnect(mac)
if not success:
self.status.setText(f"Статус: ошибка ({self._bt_service.last_error})")
else:
self.status.setText(f"Статус: отключено от {mac}")
QTimer.singleShot(300, self._update_status)
def _update_status(self):
"""Обновить текстовый статус."""
mac = self._selected_mac()
self.status.setText(self._bt_service.get_status_text(mac))
def showEvent(self, event):
"""Экран показан — запускаем режим сопряжения и мониторинг."""
super().showEvent(event)
self._refresh_discoverable()
self._bt_service.start_pairing_monitor()
def hideEvent(self, event):
"""Экран скрыт — останавливаем таймер и мониторинг."""
super().hideEvent(event)
self._discoverable_timer.stop()
self._bt_service.stop_pairing_monitor()