vor ble umbau
This commit is contained in:
247
webpage/src/lib/bluetooth.ts
Normal file
247
webpage/src/lib/bluetooth.ts
Normal file
@@ -0,0 +1,247 @@
|
||||
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 <b>${device.name}</b>`, "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 <b>" + device.name + "</b> 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);
|
||||
}
|
||||
Reference in New Issue
Block a user