mirror of
https://github.com/google/bumble.git
synced 2026-06-04 08:07:03 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 34161e5e14 | |||
| 216ce2abd0 | |||
| 431445e6a2 | |||
| d7cc546248 | |||
| 29fd19f40d | |||
| 14dfc1a501 | |||
| 938282e961 | |||
| 900c15b151 | |||
| 9ea93be723 | |||
| 894ab023c7 | |||
| 7bbb37b2da | |||
| 3fa5d320de | |||
| 16d684c199 | |||
| c28aa2ebb6 | |||
| 28586382f4 |
@@ -29,11 +29,11 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install ".[test,development,documentation]"
|
||||
python -m pip install ".[build,test,development,documentation]"
|
||||
- name: Test with pytest
|
||||
run: |
|
||||
pytest
|
||||
- name: Build
|
||||
run: |
|
||||
inv build
|
||||
inv mkdocs
|
||||
inv build.mkdocs
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
name: Upload Python Package
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -31,14 +29,8 @@ jobs:
|
||||
python -m pip install build
|
||||
- name: Build package
|
||||
run: python -m build
|
||||
- name: Publish package to Test PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
|
||||
repository_url: https://test.pypi.org/legacy/
|
||||
- name: Publish package to PyPI
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
|
||||
if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags')
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
|
||||
+47
-15
@@ -122,6 +122,8 @@ class ConsoleApp:
|
||||
},
|
||||
'read': LiveCompleter(self.known_attributes),
|
||||
'write': LiveCompleter(self.known_attributes),
|
||||
'subscribe': LiveCompleter(self.known_attributes),
|
||||
'unsubscribe': LiveCompleter(self.known_attributes),
|
||||
'quit': None,
|
||||
'exit': None
|
||||
})
|
||||
@@ -331,7 +333,7 @@ class ConsoleApp:
|
||||
|
||||
await self.show_attributes(attributes)
|
||||
|
||||
def find_attribute(self, param):
|
||||
def find_characteristic(self, param):
|
||||
parts = param.split('.')
|
||||
if len(parts) == 2:
|
||||
service_uuid = UUID(parts[0]) if parts[0] != '*' else None
|
||||
@@ -344,7 +346,10 @@ class ConsoleApp:
|
||||
elif len(parts) == 1:
|
||||
if parts[0].startswith('#'):
|
||||
attribute_handle = int(f'{parts[0][1:]}', 16)
|
||||
return attribute_handle
|
||||
for service in self.connected_peer.services:
|
||||
for characteristic in service.characteristics:
|
||||
if characteristic.handle == attribute_handle:
|
||||
return characteristic
|
||||
|
||||
async def command(self, command):
|
||||
try:
|
||||
@@ -457,13 +462,13 @@ class ConsoleApp:
|
||||
self.show_error('invalid syntax', 'expected read <attribute>')
|
||||
return
|
||||
|
||||
attribute = self.find_attribute(params[0])
|
||||
if attribute is None:
|
||||
characteristic = self.find_characteristic(params[0])
|
||||
if characteristic is None:
|
||||
self.show_error('no such characteristic')
|
||||
return
|
||||
|
||||
value = await self.connected_peer.read_value(attribute)
|
||||
self.append_to_output(f'VALUE: {value}')
|
||||
value = await characteristic.read_value()
|
||||
self.append_to_output(f'VALUE: 0x{value.hex()}')
|
||||
|
||||
async def do_write(self, params):
|
||||
if not self.connected_peer:
|
||||
@@ -482,21 +487,48 @@ class ConsoleApp:
|
||||
except ValueError:
|
||||
value = str.encode(params[1]) # must be a string
|
||||
|
||||
attribute = self.find_attribute(params[0])
|
||||
if attribute is None:
|
||||
characteristic = self.find_characteristic(params[0])
|
||||
if characteristic is None:
|
||||
self.show_error('no such characteristic')
|
||||
return
|
||||
|
||||
# use write with response if supported
|
||||
with_response = (
|
||||
(attribute.properties & Characteristic.WRITE)
|
||||
if hasattr(attribute, "properties")
|
||||
else False
|
||||
with_response = characteristic.properties & Characteristic.WRITE
|
||||
await characteristic.write_value(value, with_response=with_response)
|
||||
|
||||
async def do_subscribe(self, params):
|
||||
if not self.connected_peer:
|
||||
self.show_error('not connected')
|
||||
return
|
||||
|
||||
if len(params) != 1:
|
||||
self.show_error('invalid syntax', 'expected subscribe <attribute>')
|
||||
return
|
||||
|
||||
characteristic = self.find_characteristic(params[0])
|
||||
if characteristic is None:
|
||||
self.show_error('no such characteristic')
|
||||
return
|
||||
|
||||
await characteristic.subscribe(
|
||||
lambda value: self.append_to_output(f"{characteristic} VALUE: 0x{value.hex()}"),
|
||||
)
|
||||
|
||||
await self.connected_peer.write_value(
|
||||
attribute, value, with_response=with_response
|
||||
)
|
||||
async def do_unsubscribe(self, params):
|
||||
if not self.connected_peer:
|
||||
self.show_error('not connected')
|
||||
return
|
||||
|
||||
if len(params) != 1:
|
||||
self.show_error('invalid syntax', 'expected subscribe <attribute>')
|
||||
return
|
||||
|
||||
characteristic = self.find_characteristic(params[0])
|
||||
if characteristic is None:
|
||||
self.show_error('no such characteristic')
|
||||
return
|
||||
|
||||
await characteristic.unsubscribe()
|
||||
|
||||
async def do_exit(self, params):
|
||||
self.ui.exit()
|
||||
|
||||
@@ -123,6 +123,9 @@ class Peer:
|
||||
async def subscribe(self, characteristic, subscriber=None):
|
||||
return await self.gatt_client.subscribe(characteristic, subscriber)
|
||||
|
||||
async def unsubscribe(self, characteristic, subscriber=None):
|
||||
return await self.gatt_client.unsubscribe(characteristic, subscriber)
|
||||
|
||||
async def read_value(self, attribute):
|
||||
return await self.gatt_client.read_value(attribute)
|
||||
|
||||
|
||||
@@ -320,6 +320,12 @@ class CharacteristicAdapter:
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.wrapped_characteristic, name)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if name in {'wrapped_characteristic', 'read_value', 'write_value', 'subscribe'}:
|
||||
super().__setattr__(name, value)
|
||||
else:
|
||||
setattr(self.wrapped_characteristic, name, value)
|
||||
|
||||
def read_encoded_value(self, connection):
|
||||
return self.encode_value(self.wrapped_characteristic.read_value(connection))
|
||||
|
||||
@@ -343,6 +349,10 @@ class CharacteristicAdapter:
|
||||
None if subscriber is None else lambda value: subscriber(self.decode_value(value))
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
wrapped = str(self.wrapped_characteristic)
|
||||
return f'{self.__class__.__name__}({wrapped})'
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
class DelegatedCharacteristicAdapter(CharacteristicAdapter):
|
||||
|
||||
+38
-3
@@ -110,6 +110,9 @@ class CharacteristicProxy(AttributeProxy):
|
||||
async def subscribe(self, subscriber=None):
|
||||
return await self.client.subscribe(self, subscriber)
|
||||
|
||||
async def unsubscribe(self, subscriber=None):
|
||||
return await self.client.unsubscribe(self, subscriber)
|
||||
|
||||
def __str__(self):
|
||||
return f'Characteristic(handle=0x{self.handle:04X}, uuid={self.uuid}, properties={Characteristic.properties_as_string(self.properties)})'
|
||||
|
||||
@@ -544,10 +547,36 @@ class Client:
|
||||
for subscriber_set in subscriber_sets:
|
||||
if subscriber is not None:
|
||||
subscriber_set.add(subscriber)
|
||||
subscriber_set.add(lambda value: characteristic.emit('update', self.connection, value))
|
||||
# Add the characteristic as a subscriber, which will result in the characteristic
|
||||
# emitting an 'update' event when a notification or indication is received
|
||||
subscriber_set.add(characteristic)
|
||||
|
||||
await self.write_value(cccd, struct.pack('<H', bits), with_response=True)
|
||||
|
||||
async def unsubscribe(self, characteristic, subscriber=None):
|
||||
# If we haven't already discovered the descriptors for this characteristic, do it now
|
||||
if not characteristic.descriptors_discovered:
|
||||
await self.discover_descriptors(characteristic)
|
||||
|
||||
# Look for the CCCD descriptor
|
||||
cccd = characteristic.get_descriptor(GATT_CLIENT_CHARACTERISTIC_CONFIGURATION_DESCRIPTOR)
|
||||
if not cccd:
|
||||
logger.warning('unsubscribing from characteristic with no CCCD descriptor')
|
||||
return
|
||||
|
||||
if subscriber is not None:
|
||||
# Remove matching subscriber from subscriber sets
|
||||
for subscriber_set in (self.notification_subscribers, self.indication_subscribers):
|
||||
subscribers = subscriber_set.get(characteristic.handle, [])
|
||||
if subscriber in subscribers:
|
||||
subscribers.remove(subscriber)
|
||||
else:
|
||||
# Remove all subscribers for this attribute from the sets!
|
||||
self.notification_subscribers.pop(characteristic.handle, None)
|
||||
self.indication_subscribers.pop(characteristic.handle, None)
|
||||
|
||||
await self.write_value(cccd, b'\x00\x00', with_response=True)
|
||||
|
||||
async def read_value(self, attribute, no_long_read=False):
|
||||
'''
|
||||
See Vol 3, Part G - 4.8.1 Read Characteristic Value
|
||||
@@ -714,7 +743,10 @@ class Client:
|
||||
if not subscribers:
|
||||
logger.warning('!!! received notification with no subscriber')
|
||||
for subscriber in subscribers:
|
||||
subscriber(notification.attribute_value)
|
||||
if callable(subscriber):
|
||||
subscriber(notification.attribute_value)
|
||||
else:
|
||||
subscriber.emit('update', notification.attribute_value)
|
||||
|
||||
def on_att_handle_value_indication(self, indication):
|
||||
# Call all subscribers
|
||||
@@ -722,7 +754,10 @@ class Client:
|
||||
if not subscribers:
|
||||
logger.warning('!!! received indication with no subscriber')
|
||||
for subscriber in subscribers:
|
||||
subscriber(indication.attribute_value)
|
||||
if callable(subscriber):
|
||||
subscriber(indication.attribute_value)
|
||||
else:
|
||||
subscriber.emit('update', indication.attribute_value)
|
||||
|
||||
# Confirm that we received the indication
|
||||
self.send_confirmation(ATT_Handle_Value_Confirmation())
|
||||
|
||||
@@ -545,13 +545,13 @@ class Server(EventEmitter):
|
||||
value = attribute.read_value(connection)
|
||||
if request.value_offset > len(value):
|
||||
response = ATT_Error_Response(
|
||||
request_opcode_in_error = request.op_code,
|
||||
request_opcode_in_error = request.op_code,
|
||||
attribute_handle_in_error = request.attribute_handle,
|
||||
error_code = ATT_INVALID_OFFSET_ERROR
|
||||
)
|
||||
elif len(value) <= mtu - 1:
|
||||
response = ATT_Error_Response(
|
||||
request_opcode_in_error = request.op_code,
|
||||
request_opcode_in_error = request.op_code,
|
||||
attribute_handle_in_error = request.attribute_handle,
|
||||
error_code = ATT_ATTRIBUTE_NOT_LONG_ERROR
|
||||
)
|
||||
|
||||
@@ -3276,6 +3276,9 @@ class HCI_Event(HCI_Packet):
|
||||
parameters = b'' if self.parameters is None else self.parameters
|
||||
return bytes([HCI_EVENT_PACKET, self.event_code, len(parameters)]) + parameters
|
||||
|
||||
def __bytes__(self):
|
||||
return self.to_bytes()
|
||||
|
||||
def __str__(self):
|
||||
result = color(self.name, 'magenta')
|
||||
if fields := getattr(self, 'fields', None):
|
||||
|
||||
+1
-1
@@ -20,11 +20,11 @@ import logging
|
||||
import struct
|
||||
|
||||
from colors import color
|
||||
from pyee import EventEmitter
|
||||
|
||||
from .core import BT_CENTRAL_ROLE, InvalidStateError, ProtocolError
|
||||
from .hci import (HCI_LE_Connection_Update_Command, HCI_Object, key_with_value,
|
||||
name_or_number)
|
||||
from .utils import EventEmitter
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Logging
|
||||
|
||||
+3
-2
@@ -17,9 +17,10 @@
|
||||
# -----------------------------------------------------------------------------
|
||||
import logging
|
||||
import asyncio
|
||||
from colors import color
|
||||
|
||||
from .utils import EventEmitter
|
||||
from colors import color
|
||||
from pyee import EventEmitter
|
||||
|
||||
from .core import InvalidStateError, ProtocolError, ConnectionError
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
@@ -7,10 +7,12 @@ The Console app is an interactive text user interface that offers a number of fu
|
||||
|
||||
* scanning
|
||||
* advertising
|
||||
* connecting to devices
|
||||
* connecting to and disconnecting from devices
|
||||
* changing connection parameters
|
||||
* enabling encryption
|
||||
* discovering GATT services and characteristics
|
||||
* read & write GATT characteristics
|
||||
* reading and writing GATT characteristics
|
||||
* subscribing to and unsubscribing from GATT characteristics
|
||||
|
||||
The console user interface has 3 main panes:
|
||||
|
||||
|
||||
@@ -16,13 +16,15 @@
|
||||
name = bumble
|
||||
use_scm_version = True
|
||||
description = Bluetooth Stack for Apps, Emulation, Test and Experimentation
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
author = Google
|
||||
author_email = tbd@tbd.com
|
||||
url = https://github.com/google/bumble
|
||||
|
||||
[options]
|
||||
python_requires = >=3.8
|
||||
packages = bumble, bumble.transport, bumble.apps, bumble.apps.link_relay
|
||||
packages = bumble, bumble.transport, bumble.profiles, bumble.apps, bumble.apps.link_relay
|
||||
package_dir =
|
||||
bumble = bumble
|
||||
bumble.apps = apps
|
||||
@@ -55,12 +57,13 @@ console_scripts =
|
||||
bumble-link-relay = bumble.apps.link_relay.link_relay:main
|
||||
|
||||
[options.extras_require]
|
||||
build =
|
||||
build >= 0.7
|
||||
test =
|
||||
pytest >= 6.2
|
||||
pytest-asyncio >= 0.17
|
||||
development =
|
||||
invoke >= 1.4
|
||||
build >= 0.7
|
||||
nox >= 2022
|
||||
documentation =
|
||||
mkdocs >= 1.2.3
|
||||
|
||||
@@ -23,35 +23,52 @@ ROOT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
ns = Collection()
|
||||
|
||||
# Building
|
||||
build_tasks = Collection()
|
||||
ns.add_collection(build_tasks, name='build')
|
||||
|
||||
ns.add_collection(build_tasks, name="build")
|
||||
|
||||
@task
|
||||
def build(ctx):
|
||||
ctx.run('python -m build')
|
||||
def build(ctx, install=False):
|
||||
if install:
|
||||
ctx.run('python -m pip install .[build]')
|
||||
|
||||
build_tasks.add_task(build, default=True, name='build')
|
||||
ctx.run("python -m build")
|
||||
|
||||
build_tasks.add_task(build, default=True)
|
||||
|
||||
@task
|
||||
def release_build(ctx):
|
||||
build(ctx, install=True)
|
||||
|
||||
build_tasks.add_task(release_build, name="release")
|
||||
|
||||
@task
|
||||
def mkdocs(ctx):
|
||||
ctx.run("mkdocs build -f docs/mkdocs/mkdocs.yml")
|
||||
|
||||
build_tasks.add_task(mkdocs, name="mkdocs")
|
||||
|
||||
# Testing
|
||||
test_tasks = Collection()
|
||||
ns.add_collection(test_tasks, name='test')
|
||||
ns.add_collection(test_tasks, name="test")
|
||||
|
||||
@task
|
||||
def test(ctx, filter=None, junit=False):
|
||||
def test(ctx, filter=None, junit=False, install=False):
|
||||
# Install the package before running the tests
|
||||
if install:
|
||||
ctx.run("python -m pip install .[test]")
|
||||
|
||||
args = ""
|
||||
if junit:
|
||||
args += "--junit-xml test-results.xml"
|
||||
if filter is not None:
|
||||
args += " -k '{}'".format(filter)
|
||||
ctx.run('python -m pytest {} {}'
|
||||
.format(os.path.join(ROOT_DIR, "tests"), args))
|
||||
|
||||
test_tasks.add_task(test, name='test', default=True)
|
||||
ctx.run("python -m pytest {} {}".format(os.path.join(ROOT_DIR, "tests"), args))
|
||||
|
||||
test_tasks.add_task(test, default=True)
|
||||
|
||||
@task
|
||||
def mkdocs(ctx):
|
||||
ctx.run('mkdocs build -f docs/mkdocs/mkdocs.yml')
|
||||
def release_test(ctx):
|
||||
test(ctx, install=True)
|
||||
|
||||
|
||||
ns.add_task(mkdocs)
|
||||
test_tasks.add_task(release_test, name="release")
|
||||
|
||||
+48
-17
@@ -419,10 +419,12 @@ async def test_subscribe_notify():
|
||||
assert(len(c) == 1)
|
||||
c3 = c[0]
|
||||
|
||||
c1._called = False
|
||||
c1._last_update = None
|
||||
|
||||
def on_c1_update(connection, value):
|
||||
c1._last_update = (connection, value)
|
||||
def on_c1_update(value):
|
||||
c1._called = True
|
||||
c1._last_update = value
|
||||
|
||||
c1.on('update', on_c1_update)
|
||||
await peer.subscribe(c1)
|
||||
@@ -434,44 +436,73 @@ async def test_subscribe_notify():
|
||||
assert(not characteristic1._last_subscription[2])
|
||||
await server.indicate_subscribers(characteristic1)
|
||||
await async_barrier()
|
||||
assert(c1._last_update is None)
|
||||
assert(not c1._called)
|
||||
await server.notify_subscribers(characteristic1)
|
||||
await async_barrier()
|
||||
assert(c1._last_update is not None)
|
||||
assert(c1._last_update[1] == characteristic1.value)
|
||||
assert(c1._called)
|
||||
assert(c1._last_update == characteristic1.value)
|
||||
|
||||
c1._called = False
|
||||
await peer.unsubscribe(c1)
|
||||
await server.notify_subscribers(characteristic1)
|
||||
assert(not c1._called)
|
||||
|
||||
c2._called = False
|
||||
c2._last_update = None
|
||||
|
||||
def on_c2_update(value):
|
||||
c2._last_update = (connection, value)
|
||||
c2._called = True
|
||||
c2._last_update = value
|
||||
|
||||
await peer.subscribe(c2, on_c2_update)
|
||||
await async_barrier()
|
||||
await server.notify_subscriber(characteristic2._last_subscription[0], characteristic2)
|
||||
await async_barrier()
|
||||
assert(c2._last_update is None)
|
||||
assert(not c2._called)
|
||||
await server.indicate_subscriber(characteristic2._last_subscription[0], characteristic2)
|
||||
await async_barrier()
|
||||
assert(c2._last_update is not None)
|
||||
assert(c2._last_update[1] == characteristic2.value)
|
||||
assert(c2._called)
|
||||
assert(c2._last_update == characteristic2.value)
|
||||
|
||||
c3._last_update = None
|
||||
c2._called = False
|
||||
await peer.unsubscribe(c2, on_c2_update)
|
||||
await server.indicate_subscriber(characteristic2._last_subscription[0], characteristic2)
|
||||
await async_barrier()
|
||||
assert(not c2._called)
|
||||
|
||||
def on_c3_update(connection, value):
|
||||
c3._last_update = (connection, value)
|
||||
def on_c3_update(value):
|
||||
c3._called = True
|
||||
c3._last_update = value
|
||||
|
||||
def on_c3_update_2(value):
|
||||
c3._called_2 = True
|
||||
c3._last_update_2 = value
|
||||
|
||||
c3.on('update', on_c3_update)
|
||||
await peer.subscribe(c3)
|
||||
await peer.subscribe(c3, on_c3_update_2)
|
||||
await async_barrier()
|
||||
await server.notify_subscriber(characteristic3._last_subscription[0], characteristic3)
|
||||
await async_barrier()
|
||||
assert(c3._last_update is not None)
|
||||
assert(c3._last_update[1] == characteristic3.value)
|
||||
assert(c3._called)
|
||||
assert(c3._last_update == characteristic3.value)
|
||||
assert(c3._called_2)
|
||||
assert(c3._last_update_2 == characteristic3.value)
|
||||
characteristic3.value = bytes([1, 2, 3])
|
||||
await server.indicate_subscriber(characteristic3._last_subscription[0], characteristic3)
|
||||
await async_barrier()
|
||||
assert(c3._last_update is not None)
|
||||
assert(c3._last_update[1] == characteristic3.value)
|
||||
assert(c3._called)
|
||||
assert(c3._last_update == characteristic3.value)
|
||||
assert(c3._called_2)
|
||||
assert(c3._last_update_2 == characteristic3.value)
|
||||
|
||||
c3._called = False
|
||||
c3._called_2 = False
|
||||
await peer.unsubscribe(c3)
|
||||
await server.notify_subscriber(characteristic3._last_subscription[0], characteristic3)
|
||||
await server.indicate_subscriber(characteristic3._last_subscription[0], characteristic3)
|
||||
await async_barrier()
|
||||
assert(not c3._called)
|
||||
assert(not c3._called_2)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
+40
-1
@@ -163,6 +163,44 @@ async def test_self_gatt():
|
||||
assert(result == c1.value)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@pytest.mark.asyncio
|
||||
async def test_self_gatt_long_read():
|
||||
# Create two devices, each with a controller, attached to the same link
|
||||
two_devices = TwoDevices()
|
||||
|
||||
# Add some GATT characteristics to device 1
|
||||
characteristics = [
|
||||
Characteristic(
|
||||
f'3A143AD7-D4A7-436B-97D6-5B62C315{i:04X}',
|
||||
Characteristic.READ,
|
||||
Characteristic.READABLE,
|
||||
bytes([x & 255 for x in range(i)])
|
||||
)
|
||||
for i in range(0, 513)
|
||||
]
|
||||
|
||||
service = Service('8140E247-04F0-42C1-BC34-534C344DAFCA', characteristics)
|
||||
two_devices.devices[1].add_service(service)
|
||||
|
||||
# Start
|
||||
await two_devices.devices[0].power_on()
|
||||
await two_devices.devices[1].power_on()
|
||||
|
||||
# Connect the two devices
|
||||
connection = await two_devices.devices[0].connect(two_devices.devices[1].random_address)
|
||||
peer = Peer(connection)
|
||||
|
||||
result = await peer.discover_service(service.uuid)
|
||||
assert(len(result) == 1)
|
||||
found_service = result[0]
|
||||
found_characteristics = await found_service.discover_characteristics()
|
||||
assert(len(found_characteristics) == 513)
|
||||
for (i, characteristic) in enumerate(found_characteristics):
|
||||
value = await characteristic.read_value()
|
||||
assert(value == characteristics[i].value)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
async def _test_self_smp_with_configs(pairing_config1, pairing_config2):
|
||||
# Create two devices, each with a controller, attached to the same link
|
||||
@@ -274,7 +312,7 @@ async def test_self_smp(io_cap, sc, mitm, key_dist):
|
||||
pairing_config2.delegate.peer_delegate = pairing_config1.delegate
|
||||
|
||||
await _test_self_smp_with_configs(pairing_config1, pairing_config2)
|
||||
|
||||
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
@@ -323,6 +361,7 @@ async def test_self_smp_wrong_pin():
|
||||
async def run_test_self():
|
||||
await test_self_connection()
|
||||
await test_self_gatt()
|
||||
await test_self_gatt_long_read()
|
||||
await test_self_smp()
|
||||
await test_self_smp_reject()
|
||||
await test_self_smp_wrong_pin()
|
||||
|
||||
Reference in New Issue
Block a user