From d43f5573a659ad0ee4b0a475abe5c3d051f4a08f Mon Sep 17 00:00:00 2001 From: Josh Wu Date: Fri, 31 Oct 2025 17:25:35 +0800 Subject: [PATCH] Upgrade websockets dependency to 15.0.1+ --- bumble/transport/common.py | 9 ++++++--- bumble/transport/ws_client.py | 4 ++-- bumble/transport/ws_server.py | 24 ++++++++++++++++-------- examples/keyboard.py | 6 +++--- examples/run_asha_sink.py | 9 ++++----- examples/run_avrcp.py | 9 ++++++--- examples/run_hfp_gateway.py | 9 ++++----- examples/run_hfp_handsfree.py | 8 ++++---- examples/run_hid_device.py | 6 +++--- examples/run_mcp_client.py | 8 ++++---- examples/run_vcp_renderer.py | 8 ++++---- pyproject.toml | 2 +- 12 files changed, 57 insertions(+), 45 deletions(-) diff --git a/bumble/transport/common.py b/bumble/transport/common.py index b0f3c8ac..208241c2 100644 --- a/bumble/transport/common.py +++ b/bumble/transport/common.py @@ -22,6 +22,7 @@ import contextlib import io import logging import struct +from collections.abc import Awaitable, Callable from typing import Any, ContextManager, Optional, Protocol from bumble import core, hci @@ -389,15 +390,17 @@ class PumpedPacketSource(ParserSource): # ----------------------------------------------------------------------------- class PumpedPacketSink: - def __init__(self, send): + pump_task: Optional[asyncio.Task[None]] + + def __init__(self, send: Callable[[bytes], Awaitable[Any]]): self.send_function = send - self.packet_queue = asyncio.Queue() + self.packet_queue = asyncio.Queue[bytes]() self.pump_task = None def on_packet(self, packet: bytes) -> None: self.packet_queue.put_nowait(packet) - def start(self): + def start(self) -> None: async def pump_packets(): while True: try: diff --git a/bumble/transport/ws_client.py b/bumble/transport/ws_client.py index 555b0138..022c2c49 100644 --- a/bumble/transport/ws_client.py +++ b/bumble/transport/ws_client.py @@ -17,7 +17,7 @@ # ----------------------------------------------------------------------------- import logging -import websockets.client +import websockets.asyncio.client from bumble.transport.common import ( PumpedPacketSink, @@ -42,7 +42,7 @@ async def open_ws_client_transport(spec: str) -> Transport: Example: ws://localhost:7681/v1/websocket/bt ''' - websocket = await websockets.client.connect(spec) + websocket = await websockets.asyncio.client.connect(spec) class WsTransport(PumpedTransport): async def close(self): diff --git a/bumble/transport/ws_server.py b/bumble/transport/ws_server.py index 519d0ab4..c2340907 100644 --- a/bumble/transport/ws_server.py +++ b/bumble/transport/ws_server.py @@ -16,8 +16,9 @@ # Imports # ----------------------------------------------------------------------------- import logging +from typing import Optional -import websockets +import websockets.asyncio.server from bumble.transport.common import ParserSource, PumpedPacketSink, Transport @@ -40,7 +41,12 @@ async def open_ws_server_transport(spec: str) -> Transport: ''' class WsServerTransport(Transport): - def __init__(self): + sink: PumpedPacketSink + source: ParserSource + connection: Optional[websockets.asyncio.server.ServerConnection] + server: Optional[websockets.asyncio.server.Server] + + def __init__(self) -> None: source = ParserSource() sink = PumpedPacketSink(self.send_packet) self.connection = None @@ -48,17 +54,19 @@ async def open_ws_server_transport(spec: str) -> Transport: super().__init__(source, sink) - async def serve(self, local_host, local_port): + async def serve(self, local_host: str, local_port: str) -> None: self.sink.start() # pylint: disable-next=no-member - self.server = await websockets.serve( - ws_handler=self.on_connection, + self.server = await websockets.asyncio.server.serve( + handler=self.on_connection, host=local_host if local_host != '_' else None, port=int(local_port), ) logger.debug(f'websocket server ready on port {local_port}') - async def on_connection(self, connection): + async def on_connection( + self, connection: websockets.asyncio.server.ServerConnection + ) -> None: logger.debug( f'new connection on {connection.local_address} ' f'from {connection.remote_address}' @@ -77,11 +85,11 @@ async def open_ws_server_transport(spec: str) -> Transport: # We're now disconnected self.connection = None - async def send_packet(self, packet): + async def send_packet(self, packet: bytes) -> None: if self.connection is None: logger.debug('no connection, dropping packet') return - return await self.connection.send(packet) + await self.connection.send(packet) local_host, local_port = spec.rsplit(':', maxsplit=1) transport = WsServerTransport() diff --git a/examples/keyboard.py b/examples/keyboard.py index 75146bad..e93de2c3 100644 --- a/examples/keyboard.py +++ b/examples/keyboard.py @@ -20,7 +20,7 @@ import json import struct import sys -import websockets +import websockets.asyncio.server import bumble.logging from bumble import data_types @@ -367,7 +367,7 @@ async def keyboard_device(device, command): if command == 'web': # Start a Websocket server to receive events from a web page - async def serve(websocket, _path): + async def serve(websocket: websockets.asyncio.server.ServerConnection): while True: try: message = await websocket.recv() @@ -398,7 +398,7 @@ async def keyboard_device(device, command): pass # pylint: disable-next=no-member - await websockets.serve(serve, 'localhost', 8989) + await websockets.asyncio.server.serve(serve, 'localhost', 8989) await asyncio.get_event_loop().create_future() else: message = bytes('hello', 'ascii') diff --git a/examples/run_asha_sink.py b/examples/run_asha_sink.py index f2e04562..252de06e 100644 --- a/examples/run_asha_sink.py +++ b/examples/run_asha_sink.py @@ -20,7 +20,7 @@ import logging import sys from typing import Optional -import websockets +import websockets.asyncio.server import bumble.logging from bumble import data_types, decoder, gatt @@ -29,12 +29,11 @@ from bumble.device import AdvertisingParameters, Device from bumble.profiles import asha from bumble.transport import open_transport -ws_connection: Optional[websockets.WebSocketServerProtocol] = None +ws_connection: Optional[websockets.asyncio.server.ServerConnection] = None g722_decoder = decoder.G722Decoder() -async def ws_server(ws_client: websockets.WebSocketServerProtocol, path: str): - del path +async def ws_server(ws_client: websockets.asyncio.server.ServerConnection): global ws_connection ws_connection = ws_client @@ -100,7 +99,7 @@ async def main() -> None: ), ) - await websockets.serve(ws_server, port=8888) + await websockets.asyncio.server.serve(ws_server, port=8888) await hci_transport.source.terminated diff --git a/examples/run_avrcp.py b/examples/run_avrcp.py index 592e21ab..21895fdc 100644 --- a/examples/run_avrcp.py +++ b/examples/run_avrcp.py @@ -21,8 +21,9 @@ import asyncio import json import logging import sys +from typing import Optional -import websockets +import websockets.asyncio.server import bumble.logging from bumble import a2dp, avc, avdtp, avrcp, utils @@ -217,6 +218,8 @@ def on_avrcp_start(avrcp_protocol: avrcp.Protocol, websocket_server: WebSocketSe # ----------------------------------------------------------------------------- class WebSocketServer: + socket: Optional[websockets.asyncio.server.ServerConnection] + def __init__( self, avrcp_protocol: avrcp.Protocol, avrcp_delegate: Delegate ) -> None: @@ -227,9 +230,9 @@ class WebSocketServer: async def start(self) -> None: # pylint: disable-next=no-member - await websockets.serve(self.serve, 'localhost', 8989) # type: ignore + await websockets.asyncio.server.serve(self.serve, 'localhost', 8989) # type: ignore - async def serve(self, socket, _path) -> None: + async def serve(self, socket: websockets.asyncio.server.ServerConnection) -> None: print('### WebSocket connected') self.socket = socket while True: diff --git a/examples/run_hfp_gateway.py b/examples/run_hfp_gateway.py index f92013e8..90984671 100644 --- a/examples/run_hfp_gateway.py +++ b/examples/run_hfp_gateway.py @@ -22,7 +22,7 @@ import logging import sys from typing import Iterable, Optional -import websockets +import websockets.asyncio.server import bumble.core import bumble.logging @@ -33,7 +33,7 @@ from bumble.transport import open_transport logger = logging.getLogger(__name__) -ws: Optional[websockets.WebSocketServerProtocol] = None +ws: Optional[websockets.asyncio.server.ServerConnection] = None ag_protocol: Optional[hfp.AgProtocol] = None source_file: Optional[io.BufferedReader] = None @@ -114,8 +114,7 @@ def on_hfp_state_change(connected: bool): send_message(type='hfp_state_change', connected=connected) -async def ws_server(ws_client: websockets.WebSocketServerProtocol, path: str): - del path +async def ws_server(ws_client: websockets.asyncio.server.ServerConnection): global ws ws = ws_client @@ -273,7 +272,7 @@ async def main() -> None: on_dlc(session) - await websockets.serve(ws_server, port=8888) + await websockets.asyncio.server.serve(ws_server, port=8888) if len(sys.argv) >= 5: global source_file diff --git a/examples/run_hfp_handsfree.py b/examples/run_hfp_handsfree.py index 1093dfc3..ff0b6a16 100644 --- a/examples/run_hfp_handsfree.py +++ b/examples/run_hfp_handsfree.py @@ -22,7 +22,7 @@ import json import sys from typing import Optional -import websockets +import websockets.asyncio.server import bumble.logging from bumble import hci, hfp, rfcomm @@ -30,7 +30,7 @@ from bumble.device import Connection, Device from bumble.hfp import HfProtocol from bumble.transport import open_transport -ws: Optional[websockets.WebSocketServerProtocol] = None +ws: Optional[websockets.asyncio.server.ServerConnection] = None hf_protocol: Optional[HfProtocol] = None @@ -143,7 +143,7 @@ async def main() -> None: await device.set_connectable(True) # Start the UI websocket server to offer a few buttons and input boxes - async def serve(websocket: websockets.WebSocketServerProtocol, _path): + async def serve(websocket: websockets.asyncio.server.ServerConnection): global ws ws = websocket async for message in websocket: @@ -166,7 +166,7 @@ async def main() -> None: response = str(await hf_protocol.query_current_calls()) await websocket.send(response) - await websockets.serve(serve, 'localhost', 8989) + await websockets.asyncio.server.serve(serve, 'localhost', 8989) await hci_transport.source.wait_for_termination() diff --git a/examples/run_hid_device.py b/examples/run_hid_device.py index a67a20ab..887c493c 100644 --- a/examples/run_hid_device.py +++ b/examples/run_hid_device.py @@ -20,7 +20,7 @@ import json import struct import sys -import websockets +import websockets.asyncio.server import bumble.logging from bumble.core import ( @@ -425,7 +425,7 @@ deviceData = DeviceData() async def keyboard_device(hid_device: HID_Device): # Start a Websocket server to receive events from a web page - async def serve(websocket, _path): + async def serve(websocket: websockets.asyncio.server.ServerConnection): global deviceData while True: try: @@ -476,7 +476,7 @@ async def keyboard_device(hid_device: HID_Device): pass # pylint: disable-next=no-member - await websockets.serve(serve, 'localhost', 8989) + await websockets.asyncio.server.serve(serve, 'localhost', 8989) await asyncio.get_event_loop().create_future() diff --git a/examples/run_mcp_client.py b/examples/run_mcp_client.py index fb5c34a1..71611303 100644 --- a/examples/run_mcp_client.py +++ b/examples/run_mcp_client.py @@ -20,7 +20,7 @@ import json import sys from typing import Optional -import websockets +import websockets.asyncio.server import bumble.logging from bumble import data_types @@ -101,7 +101,7 @@ async def main() -> None: ) device.add_service(AudioStreamControlService(device, sink_ase_id=[1])) - ws: Optional[websockets.WebSocketServerProtocol] = None + ws: Optional[websockets.asyncio.server.ServerConnection] = None mcp: Optional[MediaControlServiceProxy] = None advertising_data = bytes( @@ -162,7 +162,7 @@ async def main() -> None: device.on('connection', on_connection) - async def serve(websocket: websockets.WebSocketServerProtocol, _path): + async def serve(websocket: websockets.asyncio.server.ServerConnection): nonlocal ws ws = websocket async for message in websocket: @@ -173,7 +173,7 @@ async def main() -> None: ) ws = None - await websockets.serve(serve, 'localhost', 8989) + await websockets.asyncio.server.serve(serve, 'localhost', 8989) await hci_transport.source.terminated diff --git a/examples/run_vcp_renderer.py b/examples/run_vcp_renderer.py index 4217ac8b..42343489 100644 --- a/examples/run_vcp_renderer.py +++ b/examples/run_vcp_renderer.py @@ -21,7 +21,7 @@ import secrets import sys from typing import Optional -import websockets +import websockets.asyncio.server import bumble.logging from bumble import data_types @@ -110,7 +110,7 @@ async def main() -> None: vcs = VolumeControlService() device.add_service(vcs) - ws: Optional[websockets.WebSocketServerProtocol] = None + ws: Optional[websockets.asyncio.server.ServerConnection] = None def on_volume_state_change(): if ws: @@ -152,7 +152,7 @@ async def main() -> None: advertising_data=advertising_data, ) - async def serve(websocket: websockets.WebSocketServerProtocol, _path): + async def serve(websocket: websockets.asyncio.server.ServerConnection): nonlocal ws await websocket.send( dumps_volume_state(vcs.volume_setting, vcs.muted, vcs.change_counter) @@ -166,7 +166,7 @@ async def main() -> None: await device.notify_subscribers(vcs.volume_state) ws = None - await websockets.serve(serve, 'localhost', 8989) + await websockets.asyncio.server.serve(serve, 'localhost', 8989) await hci_transport.source.terminated diff --git a/pyproject.toml b/pyproject.toml index 184ccf07..37f450cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ dependencies = [ "pyserial-asyncio >= 0.5; platform_system!='Emscripten'", "pyserial >= 3.5; platform_system!='Emscripten'", "pyusb >= 1.2; platform_system!='Emscripten'", - "websockets == 13.1; platform_system!='Emscripten'", + "websockets >= 15.0.1; platform_system!='Emscripten'", ] [project.optional-dependencies]