Compare commits

...

5 Commits

Author SHA1 Message Date
Gilles Boccon-Gibod 34161e5e14 add test.release task to facilitate CI integration 2022-08-16 13:30:45 -07:00
Ray 216ce2abd0 Add release tasks (#6)
Added two tasks to tasks.py, release and release_tests.

Applied black formatter

authored-by: Raymundo Ramirez Mata <raymundora@google.com>
2022-08-16 11:50:30 -07:00
Gilles Boccon-Gibod 431445e6a2 fix imports (#25) 2022-08-16 11:29:56 -07:00
Michael Mogenson d7cc546248 Update supported commands in console.py docs (#24)
Co-authored-by: Michael Mogenson <mogenson@google.com>
2022-08-12 14:23:21 -07:00
Gilles Boccon-Gibod 29fd19f40d gbg/fix subscribe lambda (#23)
* don't use a lambda as a subscriber

* update tests to look at side effects instead of internals
2022-08-12 14:22:31 -07:00
8 changed files with 97 additions and 51 deletions
+2 -2
View File
@@ -29,11 +29,11 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip 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 - name: Test with pytest
run: | run: |
pytest pytest
- name: Build - name: Build
run: | run: |
inv build inv build
inv mkdocs inv build.mkdocs
+11 -3
View File
@@ -547,7 +547,9 @@ class Client:
for subscriber_set in subscriber_sets: for subscriber_set in subscriber_sets:
if subscriber is not None: if subscriber is not None:
subscriber_set.add(subscriber) 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) await self.write_value(cccd, struct.pack('<H', bits), with_response=True)
@@ -741,7 +743,10 @@ class Client:
if not subscribers: if not subscribers:
logger.warning('!!! received notification with no subscriber') logger.warning('!!! received notification with no subscriber')
for subscriber in subscribers: 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): def on_att_handle_value_indication(self, indication):
# Call all subscribers # Call all subscribers
@@ -749,7 +754,10 @@ class Client:
if not subscribers: if not subscribers:
logger.warning('!!! received indication with no subscriber') logger.warning('!!! received indication with no subscriber')
for subscriber in subscribers: 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 # Confirm that we received the indication
self.send_confirmation(ATT_Handle_Value_Confirmation()) self.send_confirmation(ATT_Handle_Value_Confirmation())
+1 -1
View File
@@ -20,11 +20,11 @@ import logging
import struct import struct
from colors import color from colors import color
from pyee import EventEmitter
from .core import BT_CENTRAL_ROLE, InvalidStateError, ProtocolError from .core import BT_CENTRAL_ROLE, InvalidStateError, ProtocolError
from .hci import (HCI_LE_Connection_Update_Command, HCI_Object, key_with_value, from .hci import (HCI_LE_Connection_Update_Command, HCI_Object, key_with_value,
name_or_number) name_or_number)
from .utils import EventEmitter
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Logging # Logging
+3 -2
View File
@@ -17,9 +17,10 @@
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
import logging import logging
import asyncio import asyncio
from colors import color
from .utils import EventEmitter from colors import color
from pyee import EventEmitter
from .core import InvalidStateError, ProtocolError, ConnectionError from .core import InvalidStateError, ProtocolError, ConnectionError
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
+4 -2
View File
@@ -7,10 +7,12 @@ The Console app is an interactive text user interface that offers a number of fu
* scanning * scanning
* advertising * advertising
* connecting to devices * connecting to and disconnecting from devices
* changing connection parameters * changing connection parameters
* enabling encryption
* discovering GATT services and characteristics * 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: The console user interface has 3 main panes:
+2 -1
View File
@@ -57,12 +57,13 @@ console_scripts =
bumble-link-relay = bumble.apps.link_relay.link_relay:main bumble-link-relay = bumble.apps.link_relay.link_relay:main
[options.extras_require] [options.extras_require]
build =
build >= 0.7
test = test =
pytest >= 6.2 pytest >= 6.2
pytest-asyncio >= 0.17 pytest-asyncio >= 0.17
development = development =
invoke >= 1.4 invoke >= 1.4
build >= 0.7
nox >= 2022 nox >= 2022
documentation = documentation =
mkdocs >= 1.2.3 mkdocs >= 1.2.3
+32 -15
View File
@@ -23,35 +23,52 @@ ROOT_DIR = os.path.dirname(os.path.realpath(__file__))
ns = Collection() ns = Collection()
# Building
build_tasks = Collection() build_tasks = Collection()
ns.add_collection(build_tasks, name='build') ns.add_collection(build_tasks, name="build")
@task @task
def build(ctx): def build(ctx, install=False):
ctx.run('python -m build') 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() test_tasks = Collection()
ns.add_collection(test_tasks, name='test') ns.add_collection(test_tasks, name="test")
@task @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 = "" args = ""
if junit: if junit:
args += "--junit-xml test-results.xml" args += "--junit-xml test-results.xml"
if filter is not None: if filter is not None:
args += " -k '{}'".format(filter) args += " -k '{}'".format(filter)
ctx.run('python -m pytest {} {}' ctx.run("python -m pytest {} {}".format(os.path.join(ROOT_DIR, "tests"), args))
.format(os.path.join(ROOT_DIR, "tests"), args))
test_tasks.add_task(test, name='test', default=True)
test_tasks.add_task(test, default=True)
@task @task
def mkdocs(ctx): def release_test(ctx):
ctx.run('mkdocs build -f docs/mkdocs/mkdocs.yml') test(ctx, install=True)
test_tasks.add_task(release_test, name="release")
ns.add_task(mkdocs)
+42 -25
View File
@@ -419,10 +419,12 @@ async def test_subscribe_notify():
assert(len(c) == 1) assert(len(c) == 1)
c3 = c[0] c3 = c[0]
c1._called = False
c1._last_update = None c1._last_update = None
def on_c1_update(connection, value): def on_c1_update(value):
c1._last_update = (connection, value) c1._called = True
c1._last_update = value
c1.on('update', on_c1_update) c1.on('update', on_c1_update)
await peer.subscribe(c1) await peer.subscribe(c1)
@@ -434,58 +436,73 @@ async def test_subscribe_notify():
assert(not characteristic1._last_subscription[2]) assert(not characteristic1._last_subscription[2])
await server.indicate_subscribers(characteristic1) await server.indicate_subscribers(characteristic1)
await async_barrier() await async_barrier()
assert(c1._last_update is None) assert(not c1._called)
await server.notify_subscribers(characteristic1) await server.notify_subscribers(characteristic1)
await async_barrier() await async_barrier()
assert(c1._last_update is not None) assert(c1._called)
assert(c1._last_update[1] == characteristic1.value) assert(c1._last_update == characteristic1.value)
assert(peer.gatt_client.notification_subscribers[c1.handle]) c1._called = False
await peer.unsubscribe(c1) await peer.unsubscribe(c1)
assert(c1.handle not in peer.gatt_client.notification_subscribers) await server.notify_subscribers(characteristic1)
assert(not c1._called)
c2._called = False
c2._last_update = None c2._last_update = None
def on_c2_update(value): 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 peer.subscribe(c2, on_c2_update)
await async_barrier() await async_barrier()
await server.notify_subscriber(characteristic2._last_subscription[0], characteristic2) await server.notify_subscriber(characteristic2._last_subscription[0], characteristic2)
await async_barrier() await async_barrier()
assert(c2._last_update is None) assert(not c2._called)
await server.indicate_subscriber(characteristic2._last_subscription[0], characteristic2) await server.indicate_subscriber(characteristic2._last_subscription[0], characteristic2)
await async_barrier() await async_barrier()
assert(c2._last_update is not None) assert(c2._called)
assert(c2._last_update[1] == characteristic2.value) assert(c2._last_update == characteristic2.value)
assert(on_c2_update in peer.gatt_client.indication_subscribers[c2.handle]) c2._called = False
await peer.unsubscribe(c2, on_c2_update) await peer.unsubscribe(c2, on_c2_update)
assert(on_c2_update not in peer.gatt_client.indication_subscribers[c2.handle]) await server.indicate_subscriber(characteristic2._last_subscription[0], characteristic2)
await async_barrier()
assert(not c2._called)
c3._last_update = None def on_c3_update(value):
c3._called = True
c3._last_update = value
def on_c3_update(connection, value): def on_c3_update_2(value):
c3._last_update = (connection, value) c3._called_2 = True
c3._last_update_2 = value
c3.on('update', on_c3_update) c3.on('update', on_c3_update)
await peer.subscribe(c3) await peer.subscribe(c3, on_c3_update_2)
await async_barrier() await async_barrier()
await server.notify_subscriber(characteristic3._last_subscription[0], characteristic3) await server.notify_subscriber(characteristic3._last_subscription[0], characteristic3)
await async_barrier() await async_barrier()
assert(c3._last_update is not None) assert(c3._called)
assert(c3._last_update[1] == characteristic3.value) assert(c3._last_update == characteristic3.value)
assert(c3._called_2)
assert(c3._last_update_2 == characteristic3.value)
characteristic3.value = bytes([1, 2, 3]) characteristic3.value = bytes([1, 2, 3])
await server.indicate_subscriber(characteristic3._last_subscription[0], characteristic3) await server.indicate_subscriber(characteristic3._last_subscription[0], characteristic3)
await async_barrier() await async_barrier()
assert(c3._last_update is not None) assert(c3._called)
assert(c3._last_update[1] == characteristic3.value) assert(c3._last_update == characteristic3.value)
assert(c3._called_2)
assert(c3._last_update_2 == characteristic3.value)
assert(peer.gatt_client.notification_subscribers[c3.handle]) c3._called = False
assert(peer.gatt_client.indication_subscribers[c3.handle]) c3._called_2 = False
await peer.unsubscribe(c3) await peer.unsubscribe(c3)
assert(c3.handle not in peer.gatt_client.notification_subscribers) await server.notify_subscriber(characteristic3._last_subscription[0], characteristic3)
assert(c3.handle not in peer.gatt_client.indication_subscribers) await server.indicate_subscriber(characteristic3._last_subscription[0], characteristic3)
await async_barrier()
assert(not c3._called)
assert(not c3._called_2)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------