Overhaul Bluetooth scanning with DBus-based BlueZ integration

Major changes:
- Add utils/bluetooth/ package with DBus scanner, fallback scanners
  (bleak, hcitool, bluetoothctl), device aggregation, and heuristics
- New unified API at /api/bluetooth/ with REST endpoints and SSE streaming
- Device observation aggregation with RSSI statistics and range bands
- Behavioral heuristics: new, persistent, beacon-like, strong+stable
- Frontend components: DeviceCard, MessageCard, RSSISparkline
- TSCM integration via get_tscm_bluetooth_snapshot() helper
- Unit tests for aggregator, heuristics, and API endpoints

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Smittix
2026-01-21 15:42:33 +00:00
parent 713c1a3470
commit 54db023520
23 changed files with 7324 additions and 143 deletions

View File

@@ -1,70 +1,83 @@
<!-- BLUETOOTH MODE -->
<div id="bluetoothMode" class="mode-content">
<!-- Capability Status -->
<div id="btCapabilityStatus" class="section" style="display: none;">
<!-- Populated by JavaScript with capability warnings -->
</div>
<div class="section">
<h3>Bluetooth Interface</h3>
<h3>Scanner Configuration</h3>
<div class="form-group">
<select id="btInterfaceSelect">
<option value="">Detecting interfaces...</option>
<label>Adapter</label>
<select id="btAdapterSelect">
<option value="">Detecting adapters...</option>
</select>
</div>
<button class="preset-btn" onclick="refreshBtInterfaces()" style="width: 100%;">
Refresh Interfaces
</button>
<div class="info-text" style="margin-top: 8px; display: grid; grid-template-columns: auto auto; gap: 4px 8px; align-items: center;" id="btToolStatus">
<span>hcitool:</span><span class="tool-status missing">Checking...</span>
<span>bluetoothctl:</span><span class="tool-status missing">Checking...</span>
</div>
</div>
<div class="section">
<h3>Scan Mode</h3>
<div class="checkbox-group" style="margin-bottom: 10px;">
<label><input type="radio" name="btScanMode" value="bluetoothctl" checked> bluetoothctl (Recommended)</label>
<label><input type="radio" name="btScanMode" value="hcitool"> hcitool (Legacy)</label>
<div class="form-group">
<label>Scan Mode</label>
<select id="btScanMode">
<option value="auto">Auto (Recommended)</option>
<option value="dbus">DBus/BlueZ</option>
<option value="bleak">Bleak Library</option>
<option value="hcitool">hcitool (Legacy)</option>
<option value="bluetoothctl">bluetoothctl</option>
</select>
</div>
<div class="form-group">
<label>Scan Duration (sec)</label>
<input type="text" id="btScanDuration" value="30" placeholder="30">
<label>Transport</label>
<select id="btTransport">
<option value="auto">Auto (BLE + Classic)</option>
<option value="le">BLE Only</option>
<option value="br_edr">Classic Only</option>
</select>
</div>
<div class="checkbox-group">
<label>
<input type="checkbox" id="btScanBLE" checked>
Scan BLE Devices
</label>
<label>
<input type="checkbox" id="btScanClassic" checked>
Scan Classic BT
</label>
<label>
<input type="checkbox" id="btDetectBeacons" checked>
Detect Trackers (AirTag/Tile)
</label>
</div>
</div>
<div class="section">
<h3>Device Actions</h3>
<div class="form-group">
<label>Target MAC</label>
<input type="text" id="btTargetMac" placeholder="AA:BB:CC:DD:EE:FF">
<label>Duration (seconds, 0 = continuous)</label>
<input type="number" id="btScanDuration" value="0" min="0" max="300" placeholder="0">
</div>
<button class="preset-btn" onclick="btEnumServices()" style="width: 100%;">
Enumerate Services
<div class="form-group">
<label>Min RSSI Filter (dBm)</label>
<input type="number" id="btMinRssi" value="-100" min="-100" max="-20" placeholder="-100">
</div>
<button class="preset-btn" onclick="btCheckCapabilities()" style="width: 100%;">
Check Capabilities
</button>
</div>
<!-- Tracker Following Alert -->
<div id="trackerFollowingAlert" class="tracker-following-alert" style="display: none;">
<!-- Populated by JavaScript -->
<div class="section">
<h3>Baseline</h3>
<div class="info-text" id="btBaselineStatus" style="margin-bottom: 8px;">
No baseline set
</div>
<div style="display: flex; gap: 8px;">
<button class="preset-btn" onclick="btSetBaseline()" style="flex: 1;">
Set Baseline
</button>
<button class="preset-btn" onclick="btClearBaseline()" style="flex: 1;">
Clear
</button>
</div>
</div>
<button class="run-btn" id="startBtBtn" onclick="startBtScan()">
<!-- Message Container for status cards -->
<div id="btMessageContainer"></div>
<button class="run-btn" id="startBtBtn" onclick="btStartScan()">
Start Scanning
</button>
<button class="stop-btn" id="stopBtBtn" onclick="stopBtScan()" style="display: none;">
<button class="stop-btn" id="stopBtBtn" onclick="btStopScan()" style="display: none;">
Stop Scanning
</button>
<button class="preset-btn" onclick="resetBtAdapter()" style="margin-top: 5px; width: 100%;">
Reset Adapter
</button>
<div class="section" style="margin-top: 10px;">
<h3>Export</h3>
<div style="display: flex; gap: 8px;">
<button class="preset-btn" onclick="btExport('csv')" style="flex: 1;">
Export CSV
</button>
<button class="preset-btn" onclick="btExport('json')" style="flex: 1;">
Export JSON
</button>
</div>
</div>
</div>