185 lines
7.1 KiB
Python
185 lines
7.1 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_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.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()
|
|
|
|
def hideEvent(self, event):
|
|
"""Экран скрыт — останавливаем таймер."""
|
|
super().hideEvent(event)
|
|
self._discoverable_timer.stop()
|