forked from auracaster/bumble_mirror
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2698d4534e | |||
| bbcd64286a | |||
| 9140afbf8c | |||
| 90a682c71b | |||
| e8737a8243 | |||
| 72fceca72e | |||
| 732294abbc | |||
| dc1204531e | |||
| 962114379c | |||
| e6913a3055 | |||
| e21d122aef | |||
| 76bca03fe3 | |||
| f1e5c9e59e | |||
| 1ceeccbbc0 |
@@ -6,6 +6,8 @@ dist/
|
||||
docs/mkdocs/site
|
||||
test-results.xml
|
||||
__pycache__
|
||||
# Vim
|
||||
.*.sw*
|
||||
# generated by setuptools_scm
|
||||
bumble/_version.py
|
||||
.vscode/launch.json
|
||||
|
||||
+9
-6
@@ -1234,6 +1234,7 @@ class Peripheral(Device.Listener, Connection.Listener):
|
||||
'cyan',
|
||||
)
|
||||
)
|
||||
|
||||
await self.connected.wait()
|
||||
logging.info(color('### Connected', 'cyan'))
|
||||
|
||||
@@ -1593,8 +1594,8 @@ def central(
|
||||
mode_factory = create_mode_factory(ctx, 'gatt-client')
|
||||
classic = ctx.obj['classic']
|
||||
|
||||
asyncio.run(
|
||||
Central(
|
||||
async def run_central():
|
||||
await Central(
|
||||
transport,
|
||||
peripheral_address,
|
||||
classic,
|
||||
@@ -1606,7 +1607,8 @@ def central(
|
||||
encrypt or authenticate,
|
||||
ctx.obj['extended_data_length'],
|
||||
).run()
|
||||
)
|
||||
|
||||
asyncio.run(run_central())
|
||||
|
||||
|
||||
@bench.command()
|
||||
@@ -1617,15 +1619,16 @@ def peripheral(ctx, transport):
|
||||
role_factory = create_role_factory(ctx, 'receiver')
|
||||
mode_factory = create_mode_factory(ctx, 'gatt-server')
|
||||
|
||||
asyncio.run(
|
||||
Peripheral(
|
||||
async def run_peripheral():
|
||||
await Peripheral(
|
||||
transport,
|
||||
ctx.obj['classic'],
|
||||
ctx.obj['extended_data_length'],
|
||||
role_factory,
|
||||
mode_factory,
|
||||
).run()
|
||||
)
|
||||
|
||||
asyncio.run(run_peripheral())
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -652,7 +652,9 @@ class SbcPacketSource:
|
||||
|
||||
# Prepare for next packets
|
||||
sequence_number += 1
|
||||
sequence_number &= 0xFFFF
|
||||
timestamp += sum((frame.sample_count for frame in frames))
|
||||
timestamp &= 0xFFFFFFFF
|
||||
frames = [frame]
|
||||
frames_size = len(frame.payload)
|
||||
else:
|
||||
|
||||
+8
-3
@@ -325,8 +325,8 @@ class MediaPacket:
|
||||
self.padding = padding
|
||||
self.extension = extension
|
||||
self.marker = marker
|
||||
self.sequence_number = sequence_number
|
||||
self.timestamp = timestamp
|
||||
self.sequence_number = sequence_number & 0xFFFF
|
||||
self.timestamp = timestamp & 0xFFFFFFFF
|
||||
self.ssrc = ssrc
|
||||
self.csrc_list = csrc_list
|
||||
self.payload_type = payload_type
|
||||
@@ -341,7 +341,12 @@ class MediaPacket:
|
||||
| len(self.csrc_list),
|
||||
self.marker << 7 | self.payload_type,
|
||||
]
|
||||
) + struct.pack('>HII', self.sequence_number, self.timestamp, self.ssrc)
|
||||
) + struct.pack(
|
||||
'>HII',
|
||||
self.sequence_number,
|
||||
self.timestamp,
|
||||
self.ssrc,
|
||||
)
|
||||
for csrc in self.csrc_list:
|
||||
header += struct.pack('>I', csrc)
|
||||
return header + self.payload
|
||||
|
||||
+5
-5
@@ -820,11 +820,11 @@ class HfProtocol(pyee.EventEmitter):
|
||||
return calls
|
||||
|
||||
async def update_ag_indicator(self, index: int, value: int):
|
||||
self.ag_indicators[index].current_status = value
|
||||
self.emit('ag_indicator', self.ag_indicators[index])
|
||||
logger.info(
|
||||
f"AG indicator updated: {self.ag_indicators[index].description}, {value}"
|
||||
)
|
||||
# CIEV is in 1-index, while ag_indicators is in 0-index.
|
||||
ag_indicator = self.ag_indicators[index - 1]
|
||||
ag_indicator.current_status = value
|
||||
self.emit('ag_indicator', ag_indicator)
|
||||
logger.info(f"AG indicator updated: {ag_indicator.description}, {value}")
|
||||
|
||||
async def handle_unsolicited(self):
|
||||
"""Handle unsolicited result codes sent by the audio gateway."""
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
from __future__ import annotations
|
||||
import asyncio
|
||||
import logging
|
||||
import socket
|
||||
|
||||
from .common import Transport, StreamPacketSource
|
||||
|
||||
@@ -28,6 +29,12 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# A pass-through function to ease mock testing.
|
||||
async def _create_server(*args, **kw_args):
|
||||
await asyncio.get_running_loop().create_server(*args, **kw_args)
|
||||
|
||||
|
||||
async def open_tcp_server_transport(spec: str) -> Transport:
|
||||
'''
|
||||
Open a TCP server transport.
|
||||
@@ -38,7 +45,22 @@ async def open_tcp_server_transport(spec: str) -> Transport:
|
||||
|
||||
Example: _:9001
|
||||
'''
|
||||
local_host, local_port = spec.split(':')
|
||||
return await _open_tcp_server_transport_impl(
|
||||
host=local_host if local_host != '_' else None, port=int(local_port)
|
||||
)
|
||||
|
||||
|
||||
async def open_tcp_server_transport_with_socket(sock: socket.socket) -> Transport:
|
||||
'''
|
||||
Open a TCP server transport with an existing socket.
|
||||
|
||||
One reason to use this variant is to let python pick an unused port.
|
||||
'''
|
||||
return await _open_tcp_server_transport_impl(sock=sock)
|
||||
|
||||
|
||||
async def _open_tcp_server_transport_impl(**kwargs) -> Transport:
|
||||
class TcpServerTransport(Transport):
|
||||
async def close(self):
|
||||
await super().close()
|
||||
@@ -77,13 +99,10 @@ async def open_tcp_server_transport(spec: str) -> Transport:
|
||||
else:
|
||||
logger.debug('no client, dropping packet')
|
||||
|
||||
local_host, local_port = spec.split(':')
|
||||
packet_source = StreamPacketSource()
|
||||
packet_sink = TcpServerPacketSink()
|
||||
await asyncio.get_running_loop().create_server(
|
||||
lambda: TcpServerProtocol(packet_source, packet_sink),
|
||||
host=local_host if local_host != '_' else None,
|
||||
port=int(local_port),
|
||||
await _create_server(
|
||||
lambda: TcpServerProtocol(packet_source, packet_sink), **kwargs
|
||||
)
|
||||
|
||||
return TcpServerTransport(packet_source, packet_sink)
|
||||
|
||||
@@ -33,18 +33,18 @@ include_package_data = True
|
||||
install_requires =
|
||||
aiohttp ~= 3.8; platform_system!='Emscripten'
|
||||
appdirs >= 1.4; platform_system!='Emscripten'
|
||||
bt-test-interfaces >= 0.0.2; platform_system!='Emscripten'
|
||||
click == 8.1.3; platform_system!='Emscripten'
|
||||
bt-test-interfaces >= 0.0.6; platform_system!='Emscripten'
|
||||
click >= 8.1.3; platform_system!='Emscripten'
|
||||
cryptography == 39; platform_system!='Emscripten'
|
||||
# Pyodide bundles a version of cryptography that is built for wasm, which may not match the
|
||||
# versions available on PyPI. Relax the version requirement since it's better than being
|
||||
# completely unable to import the package in case of version mismatch.
|
||||
cryptography >= 39.0; platform_system=='Emscripten'
|
||||
grpcio == 1.57.0; platform_system!='Emscripten'
|
||||
grpcio >= 1.62.1; platform_system!='Emscripten'
|
||||
humanize >= 4.6.0; platform_system!='Emscripten'
|
||||
libusb1 >= 2.0.1; platform_system!='Emscripten'
|
||||
libusb-package == 1.0.26.1; platform_system!='Emscripten'
|
||||
platformdirs == 3.10.0; platform_system!='Emscripten'
|
||||
platformdirs >= 3.10.0; platform_system!='Emscripten'
|
||||
prompt_toolkit >= 3.0.16; platform_system!='Emscripten'
|
||||
prettytable >= 3.6.0; platform_system!='Emscripten'
|
||||
protobuf >= 3.12.4; platform_system!='Emscripten'
|
||||
@@ -83,12 +83,12 @@ build =
|
||||
build >= 0.7
|
||||
test =
|
||||
pytest >= 8.0
|
||||
pytest-asyncio == 0.21.1
|
||||
pytest-asyncio >= 0.21.1
|
||||
pytest-html >= 3.2.0
|
||||
coverage >= 6.4
|
||||
development =
|
||||
black == 24.3
|
||||
grpcio-tools >= 1.57.0
|
||||
grpcio-tools >= 1.62.1
|
||||
invoke >= 1.7.3
|
||||
mypy == 1.8.0
|
||||
nox >= 2022
|
||||
@@ -98,8 +98,8 @@ development =
|
||||
types-invoke >= 1.7.3
|
||||
types-protobuf >= 4.21.0
|
||||
avatar =
|
||||
pandora-avatar == 0.0.8
|
||||
rootcanal == 1.9.0 ; python_version>='3.10'
|
||||
pandora-avatar == 0.0.9
|
||||
rootcanal == 1.10.0 ; python_version>='3.10'
|
||||
documentation =
|
||||
mkdocs >= 1.4.0
|
||||
mkdocs-material >= 8.5.6
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# Copyright 2024 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.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import pytest
|
||||
import socket
|
||||
import unittest
|
||||
from unittest.mock import ANY, patch
|
||||
|
||||
from bumble.transport.tcp_server import (
|
||||
open_tcp_server_transport,
|
||||
open_tcp_server_transport_with_socket,
|
||||
)
|
||||
|
||||
|
||||
class OpenTcpServerTransportTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.patcher = patch('bumble.transport.tcp_server._create_server')
|
||||
self.mock_create_server = self.patcher.start()
|
||||
|
||||
def tearDown(self):
|
||||
self.patcher.stop()
|
||||
|
||||
def test_open_with_spec(self):
|
||||
asyncio.run(open_tcp_server_transport('localhost:32100'))
|
||||
self.mock_create_server.assert_awaited_once_with(
|
||||
ANY, host='localhost', port=32100
|
||||
)
|
||||
|
||||
def test_open_with_port_only_spec(self):
|
||||
asyncio.run(open_tcp_server_transport('_:32100'))
|
||||
self.mock_create_server.assert_awaited_once_with(ANY, host=None, port=32100)
|
||||
|
||||
def test_open_with_socket(self):
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||
asyncio.run(open_tcp_server_transport_with_socket(sock=sock))
|
||||
self.mock_create_server.assert_awaited_once_with(ANY, sock=sock)
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not os.environ.get('PYTEST_NOSKIP', 0),
|
||||
reason='''\
|
||||
Not hermetic. Should only run manually with
|
||||
$ PYTEST_NOSKIP=1 pytest tests
|
||||
''',
|
||||
)
|
||||
def test_open_with_real_socket():
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||
sock.bind(('localhost', 0))
|
||||
port = sock.getsockname()[1]
|
||||
assert port != 0
|
||||
asyncio.run(open_tcp_server_transport_with_socket(sock=sock))
|
||||
Reference in New Issue
Block a user