import { get } from 'svelte/store'; import { injectDummyDevices, isConnected, isPaired, isConnecting, pairedDevices, availableDevices, activeDeviceId, saveConnectionState, loadConnectionState, targetDeviceId, resetLocal, resetRemote } from './store'; import { BLE } from './protocol/constants'; import { parseIncomingFrame } from './protocol'; import { registerTransport, handleTransportDisconnect, handleTransportConnect } from './transport'; import { addToast, clearAllToasts } from './toast'; import { SETTINGS } from './settings'; let rxCharacteristic: BluetoothRemoteGATTCharacteristic | null = null; let txCharacteristic: BluetoothRemoteGATTCharacteristic | null = null; let device: BluetoothDevice | null = null; export async function restoreSession() { try { const devices = await getPairedDevices(); if (devices.length > 0) { isPaired.set(true); startScanningAdvertisements(devices); const savedState = loadConnectionState(); if (savedState && savedState.autoConnect && savedState.transport === 'ble') { const targetDev = devices.find(d => d.id === savedState.deviceId); if (targetDev) { addToast("Versuche automatische Wiederverbindung...", "info"); await connectBuzzer(targetDev); } } else if (savedState) { targetDeviceId.set(savedState.deviceId); device = devices.find(d => d.id === savedState.deviceId) || devices[0]; } else { device = devices[0]; } } } catch (error) { console.error("Session-Wiederherstellung fehlgeschlagen:", error); } } async function startScanningAdvertisements(devices: BluetoothDevice[]) { for (const dev of devices) { // Sicherheits-Check für Mock-Objekte if (typeof dev.addEventListener !== 'function') continue; dev.addEventListener('advertisementreceived', () => { availableDevices.update(set => { const newSet = new Set(set); newSet.add(dev.id); return newSet; }); }); try { // Auch hier vorher prüfen if (typeof dev.watchAdvertisements === 'function') { await dev.watchAdvertisements(); } } catch (e) { console.warn("Scanning für Gerät nicht möglich:", dev.name); } } } export async function pairBuzzer() { try { const newDevice = await navigator.bluetooth.requestDevice({ filters: [{ services: [BLE.SERVICE_UUID] }] }); isPaired.set(true); const devices = await getPairedDevices(); startScanningAdvertisements(devices); await connectBuzzer(newDevice); } catch (error) { console.error("Pairing abgebrochen oder fehlgeschlagen", error); } } export async function connectBuzzer(targetDevice?: BluetoothDevice | Event) { console.debug("connectBuzzer aufgerufen"); if (targetDevice instanceof Event) { targetDevice = undefined; } if (get(isConnecting)) return; if (targetDevice) { if (device && device.gatt?.connected && device.id !== (targetDevice as BluetoothDevice).id) { device.gatt.disconnect(); } device = targetDevice as BluetoothDevice; } clearAllToasts(); if (!device) return; device.removeEventListener('gattserverdisconnected', handleDisconnect); device.addEventListener('gattserverdisconnected', handleDisconnect); isConnecting.set(true); console.debug("connectBuzzer: Verbindungsversuch mit", device.name); const connectionTimeout = setTimeout(() => { isConnecting.set(false); addToast("Verbindungsaufbau fehlgeschlagen (Timeout).", "error", true); handleTransportDisconnect(); }, SETTINGS.bluetooth.connectionTimeoutMs); try { const server = await device.gatt?.connect(); if (!server) throw new Error("GATT Server nicht gefunden"); const service = await server.getPrimaryService(BLE.SERVICE_UUID); rxCharacteristic = await service.getCharacteristic(BLE.RX_UUID); txCharacteristic = await service.getCharacteristic(BLE.TX_UUID); await txCharacteristic.startNotifications(); txCharacteristic.addEventListener('characteristicvaluechanged', handleIncomingData); clearTimeout(connectionTimeout); // Hardware-Setup ist fertig -> Übergabe an den Transport-Layer activeDeviceId.set(device.id); saveConnectionState({ transport: 'ble', deviceId: device.id, autoConnect: true }); targetDeviceId.set(device.id); addToast(`Verbunden mit ${device.name}`, "success"); // Führt die Requests aus und setzt $isConnected = true await handleTransportConnect(sendBleFrame); } catch (error) { clearTimeout(connectionTimeout); const errMsg = error instanceof Error ? error.message : String(error); console.error("Bluetooth Fehler während connectBuzzer:", errMsg); if (errMsg.includes("no longer in range")) { addToast("Geräte-Referenz veraltet (Out of Range). Bitte über den 'Pairen'-Button neu autorisieren.", "error", true); if (device) { const deadId = device.id; pairedDevices.update(devices => devices.filter(d => d.id !== deadId)); availableDevices.update(set => { const newSet = new Set(set); newSet.delete(deadId); return newSet; }); } } else { addToast("Verbindungsfehler: " + errMsg, "error"); } handleTransportDisconnect(); } finally { isConnecting.set(false); } } export function disconnectBuzzer() { console.debug("disconnectBuzer aufgerufen"); if (device) { saveConnectionState({ transport: 'ble', deviceId: device.id, autoConnect: false }); isConnected.set(false); if (device.gatt?.connected) { device.gatt.disconnect(); } } resetRemote(); addToast("Verbindung mit " + device.name + " getrennt", "info"); } export async function forgetDevice(targetDevice: BluetoothDevice) { console.debug("forgetDevice aufgerufen"); try { if (targetDevice.gatt?.connected) { targetDevice.gatt.disconnect(); } await targetDevice.forget(); addToast(`Gerät ${targetDevice.name} vergessen`, "success"); const devices = await getPairedDevices(); if (devices.length === 0) { isPaired.set(false); } } catch (error) { console.error("Fehler beim Löschen des Geräts:", error); addToast("Konnte Gerät nicht entfernen", "error", true); } } export async function getPairedDevices() { let rawDevices: BluetoothDevice[] = []; // 1. Physische Geräte abrufen, falls die API verfügbar ist if ('bluetooth' in navigator && 'getDevices' in navigator.bluetooth) { try { rawDevices = await navigator.bluetooth.getDevices(); } catch (error) { console.error("Fehler beim Abrufen der gekoppelten Geräte:", error); } } // 2. Physische Geräte in den Store schreiben pairedDevices.set(rawDevices); // 3. Testdaten anfügen injectDummyDevices(); // 4. Den aktualisierten Store-Inhalt (inkl. Dummies) für die weiterverarbeitenden Funktionen zurückgeben return get(pairedDevices); } function handleDisconnect() { console.debug("handleDisconnect aufgerufen"); if (get(isConnected)) { addToast("Verbindung zu Buzzer verloren", "warning"); } resetRemote(); registerTransport(null); rxCharacteristic = null; txCharacteristic = null; } function handleIncomingData(event: Event) { const target = event.target as BluetoothRemoteGATTCharacteristic; if (target.value) { parseIncomingFrame(target.value, (buffer) => sendBleFrame(buffer)); } } export async function sendBleFrame(buffer: ArrayBuffer) { // TODO: MTU Check einfügen! if (!rxCharacteristic) return; await rxCharacteristic.writeValueWithoutResponse(buffer); }