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);
}