From 99bc92d53d3d5c82a8bb2c9797700087f3863fc3 Mon Sep 17 00:00:00 2001
From: Gilles Boccon-Gibod
+
+
+ 60
+
+
+
+
\ No newline at end of file
diff --git a/web/heart_rate_monitor/heart_rate_monitor.js b/web/heart_rate_monitor/heart_rate_monitor.js
new file mode 100644
index 0000000..a1729f9
--- /dev/null
+++ b/web/heart_rate_monitor/heart_rate_monitor.js
@@ -0,0 +1,29 @@
+import {setupSimpleApp} from '../bumble.js';
+
+const logOutput = document.querySelector('#log-output');
+function logToOutput(message) {
+ console.log(message);
+ logOutput.value += message + '\n';
+}
+
+let heartRate = 60;
+const heartRateText = document.querySelector('#hr-value')
+
+function setHeartRate(newHeartRate) {
+ heartRate = newHeartRate;
+ heartRateText.innerHTML = heartRate;
+ app.set_heart_rate(heartRate);
+}
+
+// Setup the UI
+const bumbleControls = document.querySelector('#bumble-controls');
+document.querySelector('#hr-up-button').addEventListener('click', () => {
+ setHeartRate(heartRate + 1);
+})
+document.querySelector('#hr-down-button').addEventListener('click', () => {
+ setHeartRate(heartRate - 1);
+})
+
+// Setup the app
+const app = await setupSimpleApp('heart_rate_monitor.py', bumbleControls, logToOutput);
+
diff --git a/web/heart_rate_monitor/heart_rate_monitor.py b/web/heart_rate_monitor/heart_rate_monitor.py
new file mode 100644
index 0000000..7affb60
--- /dev/null
+++ b/web/heart_rate_monitor/heart_rate_monitor.py
@@ -0,0 +1,108 @@
+# Copyright 2021-2022 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# -----------------------------------------------------------------------------
+# Imports
+# -----------------------------------------------------------------------------
+import struct
+
+from bumble.core import AdvertisingData
+from bumble.device import Device
+from bumble.hci import HCI_Reset_Command
+from bumble.profiles.device_information_service import DeviceInformationService
+from bumble.profiles.heart_rate_service import HeartRateService
+from bumble.utils import AsyncRunner
+
+
+# -----------------------------------------------------------------------------
+class HeartRateMonitor:
+ def __init__(self, hci_source, hci_sink):
+ self.heart_rate = 60
+
+ self.device = Device.with_hci(
+ 'Bumble', 'F0:F1:F2:F3:F4:F5', hci_source, hci_sink
+ )
+
+ device_information_service = DeviceInformationService(
+ manufacturer_name='ACME',
+ model_number='HR-102',
+ serial_number='7654321',
+ hardware_revision='1.1.3',
+ software_revision='2.5.6',
+ system_id=(0x123456, 0x8877665544),
+ )
+
+ self.heart_rate_service = HeartRateService(
+ read_heart_rate_measurement=lambda _: HeartRateService.HeartRateMeasurement(
+ heart_rate=self.heart_rate,
+ sensor_contact_detected=True,
+ ),
+ body_sensor_location=HeartRateService.BodySensorLocation.WRIST,
+ reset_energy_expended=self.reset_energy_expended,
+ )
+
+ # Notify subscribers of the current value as soon as they subscribe
+ @self.heart_rate_service.heart_rate_measurement_characteristic.on('subscription')
+ def on_subscription(_, notify_enabled, indicate_enabled):
+ if notify_enabled or indicate_enabled:
+ self.notify_heart_rate()
+
+ self.device.add_services([device_information_service, self.heart_rate_service])
+
+ self.device.advertising_data = bytes(
+ AdvertisingData(
+ [
+ (
+ AdvertisingData.COMPLETE_LOCAL_NAME,
+ bytes('Bumble Heart', 'utf-8'),
+ ),
+ (
+ AdvertisingData.INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS,
+ bytes(self.heart_rate_service.uuid),
+ ),
+ (AdvertisingData.APPEARANCE, struct.pack('
-
-
-
-
+
+
|
@@ -25,7 +26,8 @@
IDLE
NOT CONNECTED
-
+
diff --git a/web/speaker/speaker.js b/web/speaker/speaker.js
index b94180f..12189a4 100644
--- a/web/speaker/speaker.js
+++ b/web/speaker/speaker.js
@@ -1,4 +1,4 @@
-import { loadBumble, connectWebSocketTransport } from "../bumble.js";
+import {setupSimpleApp} from '../bumble.js';
(function () {
'use strict';
@@ -8,7 +8,6 @@ import { loadBumble, connectWebSocketTransport } from "../bumble.js";
let bytesReceivedText;
let streamStateText;
let connectionStateText;
- let errorText;
let audioOnButton;
let mediaSource;
let sourceBuffer;
@@ -19,15 +18,14 @@ import { loadBumble, connectWebSocketTransport } from "../bumble.js";
let audioFrequencyData;
let packetsReceived = 0;
let bytesReceived = 0;
- let audioState = "stopped";
- let streamState = "IDLE";
+ let audioState = 'stopped';
+ let streamState = 'IDLE';
let fftCanvas;
let fftCanvasContext;
let bandwidthCanvas;
let bandwidthCanvasContext;
let bandwidthBinCount;
let bandwidthBins = [];
- let pyodide;
const FFT_WIDTH = 800;
const FFT_HEIGHT = 256;
@@ -44,18 +42,16 @@ import { loadBumble, connectWebSocketTransport } from "../bumble.js";
}
function initUI() {
- audioOnButton = document.getElementById("audioOnButton");
- codecText = document.getElementById("codecText");
- packetsReceivedText = document.getElementById("packetsReceivedText");
- bytesReceivedText = document.getElementById("bytesReceivedText");
- streamStateText = document.getElementById("streamStateText");
- errorText = document.getElementById("errorText");
- connectionStateText = document.getElementById("connectionStateText");
+ audioOnButton = document.getElementById('audioOnButton');
+ codecText = document.getElementById('codecText');
+ packetsReceivedText = document.getElementById('packetsReceivedText');
+ bytesReceivedText = document.getElementById('bytesReceivedText');
+ streamStateText = document.getElementById('streamStateText');
+ connectionStateText = document.getElementById('connectionStateText');
- audioOnButton.onclick = () => startAudio();
+ audioOnButton.onclick = startAudio;
- codecText.innerText = "AAC";
- setErrorText("");
+ codecText.innerText = 'AAC';
requestAnimationFrame(onAnimationFrame);
}
@@ -68,62 +64,36 @@ import { loadBumble, connectWebSocketTransport } from "../bumble.js";
}
function initAudioElement() {
- audioElement = document.getElementById("audio");
+ audioElement = document.getElementById('audio');
audioElement.src = URL.createObjectURL(mediaSource);
// audioElement.controls = true;
}
function initAnalyzer() {
- fftCanvas = document.getElementById("fftCanvas");
+ fftCanvas = document.getElementById('fftCanvas');
fftCanvas.width = FFT_WIDTH
fftCanvas.height = FFT_HEIGHT
fftCanvasContext = fftCanvas.getContext('2d');
- fftCanvasContext.fillStyle = "rgb(0, 0, 0)";
+ fftCanvasContext.fillStyle = 'rgb(0, 0, 0)';
fftCanvasContext.fillRect(0, 0, FFT_WIDTH, FFT_HEIGHT);
- bandwidthCanvas = document.getElementById("bandwidthCanvas");
+ bandwidthCanvas = document.getElementById('bandwidthCanvas');
bandwidthCanvas.width = BANDWIDTH_WIDTH
bandwidthCanvas.height = BANDWIDTH_HEIGHT
bandwidthCanvasContext = bandwidthCanvas.getContext('2d');
- bandwidthCanvasContext.fillStyle = "rgb(255, 255, 255)";
+ bandwidthCanvasContext.fillStyle = 'rgb(255, 255, 255)';
bandwidthCanvasContext.fillRect(0, 0, BANDWIDTH_WIDTH, BANDWIDTH_HEIGHT);
}
async function initBumble() {
- // Load pyodide
- console.log("Loading Pyodide");
- pyodide = await loadPyodide();
-
- // Load Bumble
- console.log("Loading Bumble");
- const params = (new URL(document.location)).searchParams;
- const bumblePackage = params.get("package") || "bumble";
- await loadBumble(pyodide, bumblePackage);
-
- console.log("Ready!")
-
- const hciWsUrl = params.get("hci") || "ws://localhost:9922/hci";
- try {
- // Create a WebSocket HCI transport
- let transport
- try {
- transport = await connectWebSocketTransport(pyodide, hciWsUrl);
- } catch (error) {
- console.error(error);
- setErrorText(error);
- return;
- }
-
- // Run the scanner example
- const script = await (await fetch("speaker.py")).text();
- await pyodide.runPythonAsync(script);
- const pythonMain = pyodide.globals.get("main");
- console.log("Starting speaker...");
- await pythonMain(transport.packet_source, transport.packet_sink, onEvent);
- console.log("Speaker running");
- } catch (err) {
- console.log(err);
- }
+ const bumbleControls = document.querySelector('#bumble-controls');
+ const app = await setupSimpleApp('speaker.py', bumbleControls, console.log);
+ app.on('start', onStart);
+ app.on('stop', onStop);
+ app.on('suspend', onSuspend);
+ app.on('connection', onConnection);
+ app.on('disconnection', onDisconnection);
+ app.on('audio', onAudio);
}
function startAnalyzer() {
@@ -144,15 +114,6 @@ import { loadBumble, connectWebSocketTransport } from "../bumble.js";
bandwidthBins = [];
}
- function setErrorText(message) {
- errorText.innerText = message;
- if (message.length == 0) {
- errorText.style.display = "none";
- } else {
- errorText.style.display = "inline-block";
- }
- }
-
function setStreamState(state) {
streamState = state;
streamStateText.innerText = streamState;
@@ -162,7 +123,7 @@ import { loadBumble, connectWebSocketTransport } from "../bumble.js";
// FFT
if (audioAnalyzer !== undefined) {
audioAnalyzer.getByteFrequencyData(audioFrequencyData);
- fftCanvasContext.fillStyle = "rgb(0, 0, 0)";
+ fftCanvasContext.fillStyle = 'rgb(0, 0, 0)';
fftCanvasContext.fillRect(0, 0, FFT_WIDTH, FFT_HEIGHT);
const barCount = audioFrequencyBinCount;
const barWidth = (FFT_WIDTH / audioFrequencyBinCount) - 1;
@@ -174,7 +135,7 @@ import { loadBumble, connectWebSocketTransport } from "../bumble.js";
}
// Bandwidth
- bandwidthCanvasContext.fillStyle = "rgb(255, 255, 255)";
+ bandwidthCanvasContext.fillStyle = 'rgb(255, 255, 255)';
bandwidthCanvasContext.fillRect(0, 0, BANDWIDTH_WIDTH, BANDWIDTH_HEIGHT);
bandwidthCanvasContext.fillStyle = `rgb(100, 100, 100)`;
for (let t = 0; t < bandwidthBins.length; t++) {
@@ -188,7 +149,7 @@ import { loadBumble, connectWebSocketTransport } from "../bumble.js";
function onMediaSourceOpen() {
console.log(this.readyState);
- sourceBuffer = mediaSource.addSourceBuffer("audio/aac");
+ sourceBuffer = mediaSource.addSourceBuffer('audio/aac');
}
function onMediaSourceClose() {
@@ -201,41 +162,30 @@ import { loadBumble, connectWebSocketTransport } from "../bumble.js";
async function startAudio() {
try {
- console.log("starting audio...");
+ console.log('starting audio...');
audioOnButton.disabled = true;
- audioState = "starting";
+ audioState = 'starting';
await audioElement.play();
- console.log("audio started");
- audioState = "playing";
+ console.log('audio started');
+ audioState = 'playing';
startAnalyzer();
} catch (error) {
console.error(`play failed: ${error}`);
- audioState = "stopped";
+ audioState = 'stopped';
audioOnButton.disabled = false;
}
}
- async function onEvent(name, params) {
- // Dispatch the message.
- const handlerName = `on${name.charAt(0).toUpperCase()}${name.slice(1)}`
- const handler = eventHandlers[handlerName];
- if (handler !== undefined) {
- handler(params);
- } else {
- console.warn(`unhandled event: ${name}`)
- }
- }
-
function onStart() {
- setStreamState("STARTED");
+ setStreamState('STARTED');
}
function onStop() {
- setStreamState("STOPPED");
+ setStreamState('STOPPED');
}
function onSuspend() {
- setStreamState("SUSPENDED");
+ setStreamState('SUSPENDED');
}
function onConnection(params) {
@@ -243,13 +193,13 @@ import { loadBumble, connectWebSocketTransport } from "../bumble.js";
}
function onDisconnection(params) {
- connectionStateText.innerText = "DISCONNECTED";
+ connectionStateText.innerText = 'DISCONNECTED';
}
function onAudio(python_packet) {
const packet = python_packet.toJs({create_proxies : false});
python_packet.destroy();
- if (audioState != "stopped") {
+ if (audioState != 'stopped') {
// Queue the audio packet.
sourceBuffer.appendBuffer(packet);
}
@@ -265,25 +215,7 @@ import { loadBumble, connectWebSocketTransport } from "../bumble.js";
}
}
- function onKeystoreupdate() {
- // Sync the FS
- pyodide.FS.syncfs(() => {
- console.log("FS synced out")
- });
- }
-
- const eventHandlers = {
- onStart,
- onStop,
- onSuspend,
- onConnection,
- onDisconnection,
- onAudio,
- onKeystoreupdate
- }
-
window.onload = (event) => {
init();
}
-
}());
\ No newline at end of file
diff --git a/web/speaker/speaker.py b/web/speaker/speaker.py
index d9488ce..96f233b 100644
--- a/web/speaker/speaker.py
+++ b/web/speaker/speaker.py
@@ -47,6 +47,7 @@ from bumble.a2dp import (
)
from bumble.utils import AsyncRunner
from bumble.codecs import AacAudioRtpPacket
+from bumble.hci import HCI_Reset_Command
# -----------------------------------------------------------------------------
@@ -95,15 +96,15 @@ class Speaker:
STARTED = 2
SUSPENDED = 3
- def __init__(self, hci_source, hci_sink, emit_event, codec, discover):
+ def __init__(self, hci_source, hci_sink, codec, discover):
self.hci_source = hci_source
self.hci_sink = hci_sink
- self.emit_event = emit_event
+ self.js_listeners = {}
self.codec = codec
self.discover = discover
self.device = None
self.connection = None
- self.listener = None
+ self.avdtp_listener = None
self.packets_received = 0
self.bytes_received = 0
self.stream_state = Speaker.StreamState.IDLE
@@ -164,7 +165,7 @@ class Speaker:
def on_key_store_update(self):
print("Key Store updated")
- self.emit_event('keystoreupdate', None)
+ self.emit('key_store_update')
def on_bluetooth_connection(self, connection):
print(f'Connection: {connection}')
@@ -172,7 +173,7 @@ class Speaker:
connection.on('disconnection', self.on_bluetooth_disconnection)
peer_name = '' if connection.peer_name is None else connection.peer_name
peer_address = connection.peer_address.to_string(False)
- self.emit_event(
+ self.emit(
'connection', {'peer_name': peer_name, 'peer_address': peer_address}
)
@@ -180,7 +181,7 @@ class Speaker:
print(f'Disconnection ({reason})')
self.connection = None
AsyncRunner.spawn(self.advertise())
- self.emit_event('disconnection', None)
+ self.emit('disconnection', None)
def on_avdtp_connection(self, protocol):
print('Audio Stream Open')
@@ -198,7 +199,7 @@ class Speaker:
# Listen for close events
protocol.on('close', self.on_avdtp_close)
- # Discover all endpoints on the remote device is requested
+ # Discoverall endpoints on the remote device is requested
if self.discover:
AsyncRunner.spawn(self.discover_remote_endpoints(protocol))
@@ -208,17 +209,17 @@ class Speaker:
def on_sink_start(self):
print("Sink Started")
self.stream_state = self.StreamState.STARTED
- self.emit_event('start', None)
+ self.emit('start', None)
def on_sink_stop(self):
print("Sink Stopped")
self.stream_state = self.StreamState.STOPPED
- self.emit_event('stop', None)
+ self.emit('stop', None)
def on_sink_suspend(self):
print("Sink Suspended")
self.stream_state = self.StreamState.SUSPENDED
- self.emit_event('suspend', None)
+ self.emit('suspend', None)
def on_sink_configuration(self, config):
print("Sink Configuration:")
@@ -234,7 +235,7 @@ class Speaker:
def on_rtp_packet(self, packet):
self.packets_received += 1
self.bytes_received += len(packet.payload)
- self.emit_event("audio", self.audio_extractor.extract_audio(packet))
+ self.emit("audio", self.audio_extractor.extract_audio(packet))
async def advertise(self):
await self.device.set_discoverable(True)
@@ -257,7 +258,7 @@ class Speaker:
print('*** Encryption on')
protocol = await Protocol.connect(connection)
- self.listener.set_server(connection, protocol)
+ self.avdtp_listener.set_server(connection, protocol)
self.on_avdtp_connection(protocol)
async def discover_remote_endpoints(self, protocol):
@@ -266,6 +267,13 @@ class Speaker:
for endpoint in endpoints:
print('@@@', endpoint)
+ def on(self, event_name, listener):
+ self.js_listeners[event_name] = listener
+
+ def emit(self, event_name, event=None):
+ if listener := self.js_listeners.get(event_name):
+ listener(event)
+
async def run(self, connect_address):
# Create a device
device_config = DeviceConfiguration()
@@ -296,8 +304,8 @@ class Speaker:
self.device.on('key_store_update', self.on_key_store_update)
# Create a listener to wait for AVDTP connections
- self.listener = Listener.for_device(self.device)
- self.listener.on('connection', self.on_avdtp_connection)
+ self.avdtp_listener = Listener.for_device(self.device)
+ self.avdtp_listener.on('connection', self.on_avdtp_connection)
print(f'Speaker ready to play, codec={self.codec}')
@@ -310,12 +318,19 @@ class Speaker:
return
else:
# Start being discoverable and connectable
- print("Waiting for connection...")
await self.advertise()
+ print("Waiting for connection...")
+
+ async def start(self):
+ await self.run(None)
+
+ async def stop(self):
+ # TODO: replace this once a proper reset is implemented in the lib.
+ await self.device.host.send_command(HCI_Reset_Command())
+ await self.device.power_off()
+ print('Speaker stopped')
# -----------------------------------------------------------------------------
-async def main(hci_source, hci_sink, emit_event):
- # logging.basicConfig(level='DEBUG')
- speaker = Speaker(hci_source, hci_sink, emit_event, "aac", False)
- await speaker.run(None)
+def main(hci_source, hci_sink):
+ return Speaker(hci_source, hci_sink, "aac", False)
diff --git a/web/ui.js b/web/ui.js
new file mode 100644
index 0000000..0f3ae33
--- /dev/null
+++ b/web/ui.js
@@ -0,0 +1,102 @@
+import {LitElement, html} from 'https://cdn.jsdelivr.net/gh/lit/dist@2/core/lit-core.min.js';
+
+class BumbleControls extends LitElement {
+ constructor() {
+ super();
+ this.bumbleLoaded = false;
+ this.connected = false;
+ }
+
+ render() {
+ return html`
+
+
+
+
+
+ `
+ }
+
+ get settingsHciUrlInput() {
+ return this.renderRoot.querySelector('#settings-hci-url-input');
+ }
+
+ get settingsDialog() {
+ return this.renderRoot.querySelector('#settings-dialog');
+ }
+
+ canConnect() {
+ return this.bumbleLoaded && !this.connected && this.getHciUrl();
+ }
+
+ getHciUrl() {
+ // Look for a URL parameter setting first.
+ const params = (new URL(document.location)).searchParams;
+ let hciWsUrl = params.get("hci");
+ if (hciWsUrl) {
+ return hciWsUrl;
+ }
+
+ // Try to load the setting from storage.
+ hciWsUrl = localStorage.getItem("hciWsUrl");
+ if (hciWsUrl) {
+ return hciWsUrl;
+ }
+
+ // Finally, default to nothing.
+ return null;
+ }
+
+ openSettingsDialog() {
+ const hciUrl = this.getHciUrl();
+ if (hciUrl) {
+ this.settingsHciUrlInput.value = hciUrl;
+ } else {
+ // Start with a template.
+ this.settingsHciUrlInput.value = "ws://localhost:XYZW/v1/websocket/bt"
+ }
+ this.settingsDialog.showModal();
+ }
+
+ onSettingsDialogClose() {
+ if (this.settingsDialog.returnValue === "cancel") {
+ return;
+ }
+ if (this.settingsHciUrlInput.value) {
+ localStorage.setItem("hciWsUrl", this.settingsHciUrlInput.value);
+ } else {
+ localStorage.removeItem("hciWsUrl");
+ }
+
+ this.requestUpdate();
+ }
+
+ saveSettings(event) {
+ event.preventDefault();
+ this.settingsDialog.close(this.settingsHciUrlInput.value);
+ }
+
+ async connectBluetooth() {
+ this.connected = await this.connector(this.getHciUrl());
+ this.requestUpdate();
+ }
+
+ async stop() {
+ await this.stopper();
+ this.connected = false;
+ this.requestUpdate();
+ }
+
+ onBumbleLoaded() {
+ this.bumbleLoaded = true;
+ this.requestUpdate();
+ }
+}
+customElements.define('bumble-controls', BumbleControls);
From ad13b114649efce3fc90d88c8ba1365208bb67bd Mon Sep 17 00:00:00 2001
From: Gilles Boccon-Gibod - - + + cardiology + 60 - - - \ No newline at end of file + + + + + + |
\ No newline at end of file diff --git a/web/scanner/scanner.js b/web/scanner/scanner.js new file mode 100644 index 0000000..a10e2c1 --- /dev/null +++ b/web/scanner/scanner.js @@ -0,0 +1,67 @@ +import {LitElement, html, css} from 'https://cdn.jsdelivr.net/gh/lit/dist@2/core/lit-core.min.js'; +import {setupSimpleApp} from '../bumble.js'; + + class ScanList extends LitElement { + static properties = { + listItems: {state: true}, + }; + + static styles = css` + table, th, td { + padding: 2px; + white-space: pre; + border: 1px solid black; + border-collapse: collapse; + } + `; + + constructor() { + super(); + this.listItems = []; + } + + render() { + if (this.listItems.length === 0) { + return ''; + } + return html` +
| ${i} | `)} +
|---|
| ${i[key]} | `)} +
+ `; + } +} +customElements.define('scan-list', ScanList); + +const logOutput = document.querySelector('#log-output'); +function logToOutput(message) { + console.log(message); + logOutput.value += message + '\n'; +} + +function onUpdate(scanResults) { + const items = scanResults.toJs({create_proxies : false}).map(entry => ( + { address: entry.address, address_type: entry.address_type, rssi: entry.rssi, data: entry.data } + )); + scanResults.destroy(); + scanList.listItems = items; +} + +// Setup the UI +const scanList = document.querySelector('#scan-list'); +const bumbleControls = document.querySelector('#bumble-controls'); + +// Setup the app +const app = await setupSimpleApp('scanner.py', bumbleControls, logToOutput); +app.on('update', onUpdate); diff --git a/web/scanner/scanner.py b/web/scanner/scanner.py index c0fc456..e498f7a 100644 --- a/web/scanner/scanner.py +++ b/web/scanner/scanner.py @@ -15,39 +15,58 @@ # ----------------------------------------------------------------------------- # Imports # ----------------------------------------------------------------------------- -import time - from bumble.device import Device +from bumble.hci import HCI_Reset_Command # ----------------------------------------------------------------------------- -class ScanEntry: - def __init__(self, advertisement): - self.address = advertisement.address.to_string(False) - self.address_type = ('Public', 'Random', 'Public Identity', 'Random Identity')[ - advertisement.address.address_type - ] - self.rssi = advertisement.rssi - self.data = advertisement.data.to_string("\n") +class Scanner: + class ScanEntry: + def __init__(self, advertisement): + self.address = advertisement.address.to_string(False) + self.address_type = ( + 'Public', + 'Random', + 'Public Identity', + 'Random Identity', + )[advertisement.address.address_type] + self.rssi = advertisement.rssi + self.data = advertisement.data.to_string('\n') + def __init__(self, hci_source, hci_sink): + super().__init__() + self.device = Device.with_hci( + 'Bumble', 'F0:F1:F2:F3:F4:F5', hci_source, hci_sink + ) + self.scan_entries = {} + self.listeners = {} + self.device.on('advertisement', self.on_advertisement) -# ----------------------------------------------------------------------------- -class ScannerListener(Device.Listener): - def __init__(self, callback): - self.callback = callback - self.entries = {} + async def start(self): + print('### Starting Scanner') + self.scan_entries = {} + self.emit_update() + await self.device.power_on() + await self.device.start_scanning() + print('### Scanner started') + + async def stop(self): + # TODO: replace this once a proper reset is implemented in the lib. + await self.device.host.send_command(HCI_Reset_Command()) + await self.device.power_off() + print('### Scanner stopped') + + def emit_update(self): + if listener := self.listeners.get('update'): + listener(list(self.scan_entries.values())) + + def on(self, event_name, listener): + self.listeners[event_name] = listener def on_advertisement(self, advertisement): - self.entries[advertisement.address] = ScanEntry(advertisement) - self.callback(list(self.entries.values())) - + self.scan_entries[advertisement.address] = self.ScanEntry(advertisement) + self.emit_update() # ----------------------------------------------------------------------------- -async def main(hci_source, hci_sink, callback): - print('### Starting Scanner') - device = Device.with_hci('Bumble', 'F0:F1:F2:F3:F4:F5', hci_source, hci_sink) - device.listener = ScannerListener(callback) - await device.power_on() - await device.start_scanning() - - print('### Scanner started') +def main(hci_source, hci_sink): + return Scanner(hci_source, hci_sink) diff --git a/web/speaker/speaker.css b/web/speaker/speaker.css index 988392a..9586054 100644 --- a/web/speaker/speaker.css +++ b/web/speaker/speaker.css @@ -11,7 +11,16 @@ body, h1, h2, h3, h4, h5, h6 { border: none; border-radius: 4px; padding: 8px; - display: inline-block; + display: none; + margin: 4px; +} + +#progressText { + background-color: rgb(179, 208, 146); + border: none; + border-radius: 4px; + padding: 8px; + display: none; margin: 4px; } diff --git a/web/speaker/speaker.html b/web/speaker/speaker.html index a20f084..84093ef 100644 --- a/web/speaker/speaker.html +++ b/web/speaker/speaker.html @@ -2,13 +2,14 @@
+ + - +