Add dynamic UART/BLE host connect for serve, dashboard, and WS.
Serve starts without a required port; REST/WS/UI can scan and switch transports, with BLE live ticks and UART-only OTA. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+251
-54
@@ -179,8 +179,8 @@
|
||||
<span class="navbar-brand mb-0 h1">Powerpod</span>
|
||||
<span class="d-flex align-items-center gap-2">
|
||||
<span class="badge rounded-pill me-1"
|
||||
:class="state.uart_connected ? 'badge-online' : 'badge-offline'"
|
||||
x-text="state.uart_connected ? 'UART' : 'UART off'"></span>
|
||||
:class="hostUp() ? 'badge-online' : 'badge-offline'"
|
||||
x-text="hostBadge()"></span>
|
||||
<span class="badge rounded-pill" :class="wsConnected ? 'badge-online' : 'badge-offline'"
|
||||
x-text="wsConnected ? 'WS' : 'WS off'"></span>
|
||||
<small class="text-muted ms-2" x-text="state.updated_at || '—'"></small>
|
||||
@@ -195,19 +195,94 @@
|
||||
x-text="configMsg"></div>
|
||||
</template>
|
||||
|
||||
<section class="card mb-4">
|
||||
<div class="card-header d-flex flex-wrap justify-content-between align-items-center gap-2">
|
||||
<span>Host-Verbindung</span>
|
||||
<span class="text-muted small" x-text="hostEndpointLabel()"></span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="nav nav-pills mb-3 gap-1">
|
||||
<li class="nav-item">
|
||||
<button type="button" class="nav-link"
|
||||
:class="connTab === 'uart' ? 'active' : ''"
|
||||
@click="connTab = 'uart'">UART</button>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<button type="button" class="nav-link"
|
||||
:class="connTab === 'ble' ? 'active' : ''"
|
||||
@click="connTab = 'ble'">BLE</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div x-show="connTab === 'uart'" class="d-flex flex-wrap gap-2 align-items-end">
|
||||
<div>
|
||||
<label class="form-label text-muted small mb-1" for="uart-port">Port</label>
|
||||
<select id="uart-port" class="form-control form-control-sm" style="min-width:12rem"
|
||||
x-model="uartPort" :disabled="hostBusy">
|
||||
<option value="">— Port wählen —</option>
|
||||
<template x-for="p in serialPorts" :key="p">
|
||||
<option :value="p" x-text="p"></option>
|
||||
</template>
|
||||
</select>
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm"
|
||||
@click="refreshSerialPorts()" :disabled="hostBusy">Aktualisieren</button>
|
||||
<button type="button" class="btn btn-primary btn-sm"
|
||||
@click="connectUART()" :disabled="hostBusy || !uartPort">Connect</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm"
|
||||
@click="disconnectHost()" :disabled="hostBusy || !hostUp()">Disconnect</button>
|
||||
</div>
|
||||
|
||||
<div x-show="connTab === 'ble'">
|
||||
<div class="d-flex flex-wrap gap-2 align-items-center mb-2">
|
||||
<button type="button" class="btn btn-primary btn-sm"
|
||||
@click="scanBLE()" :disabled="hostBusy || bleScanning"
|
||||
x-text="bleScanning ? 'Scan…' : 'Scan'"></button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm"
|
||||
@click="disconnectHost()" :disabled="hostBusy || !hostUp()">Disconnect</button>
|
||||
<span class="text-muted small" x-show="bleScanErr" x-text="bleScanErr"></span>
|
||||
</div>
|
||||
<div class="table-responsive" x-show="bleDevices.length">
|
||||
<table class="table table-sm pp-table mb-0">
|
||||
<thead>
|
||||
<tr><th>Name</th><th>Adresse</th><th>RSSI</th><th></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template x-for="d in bleDevices" :key="d.address">
|
||||
<tr>
|
||||
<td x-text="d.name || '—'"></td>
|
||||
<td class="mac" x-text="d.address"></td>
|
||||
<td x-text="d.rssi"></td>
|
||||
<td class="text-end">
|
||||
<button type="button" class="btn btn-sm btn-primary"
|
||||
@click="connectBLE(d)" :disabled="hostBusy">Connect</button>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="text-muted small mb-0" x-show="!bleDevices.length && !bleScanning">
|
||||
Scan startet eine kurze Suche (trennt aktive BLE-Verbindung).
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="row g-4">
|
||||
<section class="col-lg-4">
|
||||
<div class="card h-100">
|
||||
<div class="card-header">Master (BMA456 lokal)</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted small mb-2" x-text="'UART ' + (state.serial_port || '—')"></p>
|
||||
<p class="text-muted small mb-2"
|
||||
x-text="(state.host_transport || 'none').toUpperCase() + ' ' + (state.host_endpoint || state.serial_port || '—')"></p>
|
||||
<p class="text-muted small mb-3">Deadzone nur am Master — Slaves bleiben unverändert.</p>
|
||||
<template x-if="!state.uart_connected">
|
||||
<template x-if="!hostUp()">
|
||||
<div class="alert alert-warning py-2 mb-2"
|
||||
x-text="state.serial_error || 'UART disconnected — reconnecting…'"></div>
|
||||
x-text="state.serial_error || 'Host nicht verbunden — oben UART oder BLE wählen.'"></div>
|
||||
</template>
|
||||
<template x-if="state.uart_connected && !state.serial_ok">
|
||||
<div class="alert alert-danger py-2" x-text="state.serial_error || 'Serial error'"></div>
|
||||
<template x-if="hostUp() && !state.serial_ok">
|
||||
<div class="alert alert-danger py-2" x-text="state.serial_error || 'Host error'"></div>
|
||||
</template>
|
||||
<template x-if="state.master?.ok">
|
||||
<dl class="row mb-0">
|
||||
@@ -242,26 +317,26 @@
|
||||
min="0" max="4095" step="1"
|
||||
placeholder="z. B. 100"
|
||||
x-model.number="masterDz"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
:disabled="busy || !hostUp()">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm"
|
||||
@click="readMasterDeadzone()"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
:disabled="busy || !hostUp()">
|
||||
Lesen
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary btn-sm"
|
||||
@click="setMasterDeadzone()"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
:disabled="busy || !hostUp()">
|
||||
Setzen
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-warning btn-sm"
|
||||
@click="findMe()"
|
||||
:disabled="busy || !state.uart_connected"
|
||||
:disabled="busy || !hostUp()"
|
||||
title="LED-Ring: Rot/Grün/Blau je 3×">
|
||||
Find me
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm"
|
||||
@click="restart(0)"
|
||||
:disabled="busy || !state.uart_connected"
|
||||
:disabled="busy || !hostUp()"
|
||||
title="Master neu starten">
|
||||
Restart
|
||||
</button>
|
||||
@@ -272,7 +347,7 @@
|
||||
<div class="d-flex flex-wrap gap-2 align-items-end">
|
||||
<select id="master-log-level" class="form-select form-select-sm config-input"
|
||||
x-model.number="masterLogLevel"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
:disabled="busy || !hostUp()">
|
||||
<option value="0">None (aus)</option>
|
||||
<option value="1">Error</option>
|
||||
<option value="2">Warn</option>
|
||||
@@ -282,12 +357,12 @@
|
||||
</select>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm"
|
||||
@click="readMasterLogLevel()"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
:disabled="busy || !hostUp()">
|
||||
Lesen
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary btn-sm"
|
||||
@click="setMasterLogLevel()"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
:disabled="busy || !hostUp()">
|
||||
Setzen
|
||||
</button>
|
||||
</div>
|
||||
@@ -297,65 +372,65 @@
|
||||
<label class="form-label text-muted mb-0" for="tap-sens">sens (0–15)</label>
|
||||
<input id="tap-sens" type="number" class="form-control form-control-sm"
|
||||
min="0" max="15" x-model.number="masterTapCfg.tap_sens_thres"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
:disabled="busy || !hostUp()">
|
||||
</div>
|
||||
<div class="col-6 col-md-4">
|
||||
<label class="form-label text-muted mb-0" for="tap-max-gest">max_gest (×5ms)</label>
|
||||
<input id="tap-max-gest" type="number" class="form-control form-control-sm"
|
||||
min="0" max="65535" x-model.number="masterTapCfg.max_gest_dur"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
:disabled="busy || !hostUp()">
|
||||
</div>
|
||||
<div class="col-6 col-md-4">
|
||||
<label class="form-label text-muted mb-0" for="tap-shock">shock (×5ms)</label>
|
||||
<input id="tap-shock" type="number" class="form-control form-control-sm"
|
||||
min="0" max="65535" x-model.number="masterTapCfg.tap_shock_dur"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
:disabled="busy || !hostUp()">
|
||||
</div>
|
||||
<div class="col-6 col-md-4">
|
||||
<label class="form-label text-muted mb-0" for="tap-quiet">quiet (×5ms)</label>
|
||||
<input id="tap-quiet" type="number" class="form-control form-control-sm"
|
||||
min="0" max="65535" x-model.number="masterTapCfg.quite_time_after_gest"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
:disabled="busy || !hostUp()">
|
||||
</div>
|
||||
<div class="col-6 col-md-4">
|
||||
<label class="form-label text-muted mb-0" for="tap-wait">wait (0|1)</label>
|
||||
<input id="tap-wait" type="number" class="form-control form-control-sm"
|
||||
min="0" max="1" x-model.number="masterTapCfg.wait_for_timeout"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
:disabled="busy || !hostUp()">
|
||||
</div>
|
||||
<div class="col-6 col-md-4">
|
||||
<label class="form-label text-muted mb-0" for="tap-axis">axis (0=X…2=Z)</label>
|
||||
<input id="tap-axis" type="number" class="form-control form-control-sm"
|
||||
min="0" max="2" x-model.number="masterTapCfg.axis_sel"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
:disabled="busy || !hostUp()">
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-3 align-items-center mt-2 small">
|
||||
<label class="tap-toggle mb-0">
|
||||
<input type="checkbox" x-model="masterTapCfg.enable_single"
|
||||
:disabled="busy || !state.uart_connected"> Single
|
||||
:disabled="busy || !hostUp()"> Single
|
||||
</label>
|
||||
<label class="tap-toggle mb-0">
|
||||
<input type="checkbox" x-model="masterTapCfg.enable_double"
|
||||
:disabled="busy || !state.uart_connected"> Double
|
||||
:disabled="busy || !hostUp()"> Double
|
||||
</label>
|
||||
<label class="tap-toggle mb-0">
|
||||
<input type="checkbox" x-model="masterTapCfg.enable_triple"
|
||||
:disabled="busy || !state.uart_connected"> Triple
|
||||
:disabled="busy || !hostUp()"> Triple
|
||||
</label>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm"
|
||||
@click="readMasterTapConfig()"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
:disabled="busy || !hostUp()">
|
||||
Lesen
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary btn-sm"
|
||||
@click="setMasterTapConfig()"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
:disabled="busy || !hostUp()">
|
||||
Setzen
|
||||
</button>
|
||||
</div>
|
||||
<p class="text-muted small mt-2 mb-0" x-show="!state.uart_connected">
|
||||
UART nicht verbunden — Eingabe gesperrt.
|
||||
<p class="text-muted small mt-2 mb-0" x-show="!hostUp()">
|
||||
Host nicht verbunden — Eingabe gesperrt.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -373,12 +448,12 @@
|
||||
:disabled="busy">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm"
|
||||
@click="setDeadzoneAll(allDz)"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
:disabled="busy || !hostUp()">
|
||||
Alle Slaves
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm"
|
||||
@click="setTapConfigAll()"
|
||||
:disabled="busy || !state.uart_connected"
|
||||
:disabled="busy || !hostUp()"
|
||||
title="Master-Tap-Config an alle Slaves (ESP-NOW)">
|
||||
Tap-Cfg alle
|
||||
</button>
|
||||
@@ -395,7 +470,7 @@
|
||||
class="btn btn-sm"
|
||||
:class="state.live_stream ? 'btn-warning' : 'btn-success'"
|
||||
@click="setLiveStream(!state.live_stream)"
|
||||
:disabled="busy || !state.uart_connected || !(state.clients || []).length"
|
||||
:disabled="busy || !hostUp() || !(state.clients || []).length"
|
||||
x-text="state.live_stream ? 'Live-Stream aus' : 'Live-Stream an'"></button>
|
||||
<span class="text-muted small">Tap alle Slaves:</span>
|
||||
<label class="tap-toggle"><input type="checkbox" x-model="allTapSingle" :disabled="busy"> S</label>
|
||||
@@ -403,7 +478,7 @@
|
||||
<label class="tap-toggle"><input type="checkbox" x-model="allTapTriple" :disabled="busy"> T</label>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm"
|
||||
@click="setTapNotifyAll(allTapSingle, allTapDouble, allTapTriple)"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
:disabled="busy || !hostUp()">
|
||||
Tap setzen
|
||||
</button>
|
||||
</div>
|
||||
@@ -445,7 +520,7 @@
|
||||
<span x-text="formatTapCfg(c.tap_config)" :title="tapCfgTitle(c.tap_config)"></span>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm ms-1 py-0 px-1"
|
||||
@click="setTapConfig(c.id, masterTapCfg)"
|
||||
:disabled="busy || !state.uart_connected || !c.available"
|
||||
:disabled="busy || !hostUp() || !c.available"
|
||||
title="Master-Tap-Config an diesen Slave">
|
||||
Set
|
||||
</button>
|
||||
@@ -462,7 +537,7 @@
|
||||
class="btn btn-sm"
|
||||
:class="c.accel_stream ? 'btn-warning' : 'btn-outline-success'"
|
||||
@click="setAccelStream(c.id, !c.accel_stream)"
|
||||
:disabled="busy || !state.uart_connected || !c.available"
|
||||
:disabled="busy || !hostUp() || !c.available"
|
||||
x-text="c.accel_stream ? 'Aus' : 'An'"
|
||||
title="ESP-NOW Accel-Stream auf Slave"></button>
|
||||
</td>
|
||||
@@ -472,19 +547,19 @@
|
||||
<input type="checkbox"
|
||||
:checked="c.tap_notify_single"
|
||||
@change="setTapNotify(c.id, $event.target.checked, c.tap_notify_double, c.tap_notify_triple)"
|
||||
:disabled="busy || !state.uart_connected || !c.available"> S
|
||||
:disabled="busy || !hostUp() || !c.available"> S
|
||||
</label>
|
||||
<label class="tap-toggle" title="Double tap">
|
||||
<input type="checkbox"
|
||||
:checked="c.tap_notify_double"
|
||||
@change="setTapNotify(c.id, c.tap_notify_single, $event.target.checked, c.tap_notify_triple)"
|
||||
:disabled="busy || !state.uart_connected || !c.available"> D
|
||||
:disabled="busy || !hostUp() || !c.available"> D
|
||||
</label>
|
||||
<label class="tap-toggle" title="Triple tap">
|
||||
<input type="checkbox"
|
||||
:checked="c.tap_notify_triple"
|
||||
@change="setTapNotify(c.id, c.tap_notify_single, c.tap_notify_double, $event.target.checked)"
|
||||
:disabled="busy || !state.uart_connected || !c.available"> T
|
||||
:disabled="busy || !hostUp() || !c.available"> T
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
@@ -500,36 +575,36 @@
|
||||
:disabled="busy">
|
||||
<button type="button" class="btn btn-primary btn-sm"
|
||||
@click="setDeadzone(c.id, slaveDz[c.id] ?? c.deadzone ?? 100)"
|
||||
:disabled="busy || !state.uart_connected || !c.available">
|
||||
:disabled="busy || !hostUp() || !c.available">
|
||||
Setzen
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm"
|
||||
@click="unicastTest(c.id)"
|
||||
:disabled="busy || !state.uart_connected || !c.available"
|
||||
:disabled="busy || !hostUp() || !c.available"
|
||||
title="ESP-NOW Unicast-Test">
|
||||
Test
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-info btn-sm"
|
||||
@click="echoPing(c.id)"
|
||||
:disabled="busy || !state.uart_connected || !c.available"
|
||||
:disabled="busy || !hostUp() || !c.available"
|
||||
title="ESP-NOW Timestamp-Echo (Round-Trip-Latenz)">
|
||||
Ping
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-info btn-sm"
|
||||
@click="ledRing({ clientId: c.id })"
|
||||
:disabled="busy || !state.uart_connected || !c.available"
|
||||
:disabled="busy || !hostUp() || !c.available"
|
||||
title="LED-Ring (aktueller Modus)">
|
||||
LED
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-warning btn-sm"
|
||||
@click="ledRing({ clientId: c.id, mode: 'find-me' })"
|
||||
:disabled="busy || !state.uart_connected || !c.available"
|
||||
:disabled="busy || !hostUp() || !c.available"
|
||||
title="Find me">
|
||||
Find
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm"
|
||||
@click="restart(c.id)"
|
||||
:disabled="busy || !state.uart_connected || !c.available"
|
||||
:disabled="busy || !hostUp() || !c.available"
|
||||
title="Neustart per ESP-NOW">
|
||||
Restart
|
||||
</button>
|
||||
@@ -602,17 +677,17 @@
|
||||
<div class="col-md-4 d-flex flex-wrap gap-2">
|
||||
<button type="button" class="btn btn-primary btn-sm"
|
||||
@click="ledRing({ clientId: 0 })"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
:disabled="busy || !hostUp()">
|
||||
Master
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-primary btn-sm"
|
||||
@click="ledRing({ allClients: true, slavesOnly: true })"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
:disabled="busy || !hostUp()">
|
||||
Alle Slaves
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm"
|
||||
@click="ledRing({ allClients: true })"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
:disabled="busy || !hostUp()">
|
||||
Alle + Master
|
||||
</button>
|
||||
</div>
|
||||
@@ -621,23 +696,23 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="col-12">
|
||||
<section class="col-12" x-show="isUARTHost() || !hostUp()">
|
||||
<div class="card">
|
||||
<div class="card-header">Firmware OTA (A/B)</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted small mb-3">
|
||||
Lädt eine <code>.bin</code> auf den Master (UART), danach verteilt die Firmware automatisch per ESP-NOW an alle verfügbaren Slaves.
|
||||
Während des Uploads pausiert das Live-Polling.
|
||||
Lädt eine <code>.bin</code> auf den Master (nur UART-Host, DIP8=0), danach verteilt die Firmware automatisch per ESP-NOW an alle verfügbaren Slaves.
|
||||
Während des Uploads pausiert das Live-Polling. Bei BLE-Host ist OTA nicht verfügbar.
|
||||
</p>
|
||||
<div class="mb-3">
|
||||
<input type="file" class="form-control form-control-sm" accept=".bin,application/octet-stream"
|
||||
@change="otaFile = $event.target.files[0]"
|
||||
:disabled="ota.active || busy || !state.uart_connected">
|
||||
:disabled="ota.active || busy || !isUARTHost()">
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2 align-items-center mb-2">
|
||||
<button type="button" class="btn btn-primary btn-sm"
|
||||
@click="uploadOTA()"
|
||||
:disabled="ota.active || busy || !state.uart_connected || !otaFile">
|
||||
:disabled="ota.active || busy || !isUARTHost() || !otaFile">
|
||||
Firmware hochladen
|
||||
</button>
|
||||
<span class="text-muted small" x-show="otaFile"
|
||||
@@ -666,7 +741,7 @@
|
||||
<td class="ota-restart-col text-end">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm"
|
||||
@click="restart(0)"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
:disabled="busy || !hostUp()">
|
||||
Restart
|
||||
</button>
|
||||
</td>
|
||||
@@ -703,7 +778,7 @@
|
||||
<td class="ota-restart-col text-end">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm"
|
||||
@click="restart(row.id)"
|
||||
:disabled="busy || !state.uart_connected">
|
||||
:disabled="busy || !hostUp()">
|
||||
Restart
|
||||
</button>
|
||||
</td>
|
||||
@@ -733,6 +808,13 @@
|
||||
state: { master: {}, clients: [] },
|
||||
ws: null,
|
||||
wsConnected: false,
|
||||
connTab: 'uart',
|
||||
uartPort: '',
|
||||
serialPorts: [],
|
||||
bleDevices: [],
|
||||
bleScanning: false,
|
||||
bleScanErr: '',
|
||||
hostBusy: false,
|
||||
masterDz: 100,
|
||||
masterDzSeeded: false,
|
||||
masterLogLevel: 0,
|
||||
@@ -779,9 +861,121 @@
|
||||
blinkMs: 350,
|
||||
blinkCount: 1
|
||||
},
|
||||
hostUp() {
|
||||
return !!(this.state?.host_connected || this.state?.uart_connected);
|
||||
},
|
||||
isUARTHost() {
|
||||
return this.hostUp() && (this.state?.host_transport || 'uart') === 'uart';
|
||||
},
|
||||
hostBadge() {
|
||||
const t = this.state?.host_transport || 'none';
|
||||
if (!this.hostUp()) return 'off';
|
||||
if (t === 'ble') return 'BLE';
|
||||
if (t === 'uart') return 'UART';
|
||||
return 'on';
|
||||
},
|
||||
hostEndpointLabel() {
|
||||
const ep = this.state?.host_endpoint || this.state?.serial_port || '';
|
||||
const t = this.state?.host_transport || 'none';
|
||||
return ep ? `${t}: ${ep}` : t;
|
||||
},
|
||||
async refreshSerialPorts() {
|
||||
try {
|
||||
const r = await fetch('/api/serial/ports');
|
||||
const data = await r.json();
|
||||
this.serialPorts = data.ports || [];
|
||||
if (!this.uartPort && this.serialPorts.length) {
|
||||
this.uartPort = this.serialPorts[0];
|
||||
}
|
||||
} catch (_) {
|
||||
this.serialPorts = [];
|
||||
}
|
||||
},
|
||||
async connectUART() {
|
||||
if (!this.uartPort) return;
|
||||
this.hostBusy = true;
|
||||
this.bleScanErr = '';
|
||||
try {
|
||||
const r = await fetch('/api/host/connect', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ transport: 'uart', port: this.uartPort }),
|
||||
});
|
||||
const data = await r.json();
|
||||
if (!r.ok || data.error) {
|
||||
this.flash(data.error || 'UART connect failed', false);
|
||||
} else {
|
||||
this.flash('UART verbunden: ' + (data.endpoint || this.uartPort), true);
|
||||
}
|
||||
} catch (e) {
|
||||
this.flash(String(e), false);
|
||||
} finally {
|
||||
this.hostBusy = false;
|
||||
}
|
||||
},
|
||||
async scanBLE() {
|
||||
this.bleScanning = true;
|
||||
this.bleScanErr = '';
|
||||
this.bleDevices = [];
|
||||
this.hostBusy = true;
|
||||
try {
|
||||
const r = await fetch('/api/ble/scan', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ timeout_s: 5 }),
|
||||
});
|
||||
const data = await r.json();
|
||||
if (data.error) this.bleScanErr = data.error;
|
||||
this.bleDevices = (data.devices || []).slice().sort((a, b) => (b.rssi || 0) - (a.rssi || 0));
|
||||
} catch (e) {
|
||||
this.bleScanErr = String(e);
|
||||
} finally {
|
||||
this.bleScanning = false;
|
||||
this.hostBusy = false;
|
||||
}
|
||||
},
|
||||
async connectBLE(d) {
|
||||
if (!d?.address) return;
|
||||
this.hostBusy = true;
|
||||
this.bleScanErr = '';
|
||||
try {
|
||||
const r = await fetch('/api/host/connect', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
transport: 'ble',
|
||||
address: d.address,
|
||||
name: d.name || '',
|
||||
}),
|
||||
});
|
||||
const data = await r.json();
|
||||
if (!r.ok || data.error) {
|
||||
this.flash(data.error || 'BLE connect failed', false);
|
||||
} else {
|
||||
this.flash('BLE verbunden: ' + (data.endpoint || d.address), true);
|
||||
this.connTab = 'ble';
|
||||
}
|
||||
} catch (e) {
|
||||
this.flash(String(e), false);
|
||||
} finally {
|
||||
this.hostBusy = false;
|
||||
}
|
||||
},
|
||||
async disconnectHost() {
|
||||
this.hostBusy = true;
|
||||
try {
|
||||
await fetch('/api/host/disconnect', { method: 'POST' });
|
||||
this.flash('Host getrennt', true);
|
||||
} catch (e) {
|
||||
this.flash(String(e), false);
|
||||
} finally {
|
||||
this.hostBusy = false;
|
||||
}
|
||||
},
|
||||
connect() {
|
||||
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const url = proto + '//' + location.host + '/ws';
|
||||
this.refreshSerialPorts();
|
||||
if (this._batteryTimer) clearInterval(this._batteryTimer);
|
||||
this._batteryTimer = setInterval(() => this.refreshBattery(), 5000);
|
||||
if (this._tapTimer) clearInterval(this._tapTimer);
|
||||
@@ -791,6 +985,7 @@
|
||||
this.ws.onopen = () => {
|
||||
this.wsConnected = true;
|
||||
this.refreshBattery();
|
||||
this.refreshSerialPorts();
|
||||
};
|
||||
this.ws.onclose = () => {
|
||||
this.wsConnected = false;
|
||||
@@ -809,6 +1004,8 @@
|
||||
}
|
||||
const prev = this.state;
|
||||
this.state = msg;
|
||||
if (msg.host_transport === 'ble') this.connTab = 'ble';
|
||||
else if (msg.host_transport === 'uart') this.connTab = 'uart';
|
||||
this.preserveBatteryInState(prev, this.state);
|
||||
this.syncTapDisplay(msg.clients || []);
|
||||
if (!this.masterDzSeeded && msg.master?.deadzone != null) {
|
||||
@@ -898,7 +1095,7 @@
|
||||
};
|
||||
},
|
||||
async refreshBattery() {
|
||||
if (!this.state?.uart_connected) return;
|
||||
if (!this.hostUp()) return;
|
||||
try {
|
||||
const r = await fetch('/api/battery?all_clients=1');
|
||||
if (!r.ok) return;
|
||||
|
||||
Reference in New Issue
Block a user