Compare commits

..

1 Commits

Author SHA1 Message Date
Gilles Boccon-Gibod 739cd33e35 add missing package entry 2022-08-04 14:14:14 -07:00
5 changed files with 39 additions and 140 deletions
+4 -4
View File
@@ -1,9 +1,9 @@
name: Upload Python Package
on:
release:
types: [published]
push:
branches: [ main ]
permissions:
contents: read
@@ -30,7 +30,7 @@ jobs:
- name: Build package
run: python -m build
- name: Publish package to PyPI
if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags')
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
+15 -47
View File
@@ -122,8 +122,6 @@ 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
})
@@ -333,7 +331,7 @@ class ConsoleApp:
await self.show_attributes(attributes)
def find_characteristic(self, param):
def find_attribute(self, param):
parts = param.split('.')
if len(parts) == 2:
service_uuid = UUID(parts[0]) if parts[0] != '*' else None
@@ -346,10 +344,7 @@ class ConsoleApp:
elif len(parts) == 1:
if parts[0].startswith('#'):
attribute_handle = int(f'{parts[0][1:]}', 16)
for service in self.connected_peer.services:
for characteristic in service.characteristics:
if characteristic.handle == attribute_handle:
return characteristic
return attribute_handle
async def command(self, command):
try:
@@ -462,13 +457,13 @@ class ConsoleApp:
self.show_error('invalid syntax', 'expected read <attribute>')
return
characteristic = self.find_characteristic(params[0])
if characteristic is None:
attribute = self.find_attribute(params[0])
if attribute is None:
self.show_error('no such characteristic')
return
value = await characteristic.read_value()
self.append_to_output(f'VALUE: 0x{value.hex()}')
value = await self.connected_peer.read_value(attribute)
self.append_to_output(f'VALUE: {value}')
async def do_write(self, params):
if not self.connected_peer:
@@ -487,48 +482,21 @@ class ConsoleApp:
except ValueError:
value = str.encode(params[1]) # must be a string
characteristic = self.find_characteristic(params[0])
if characteristic is None:
attribute = self.find_attribute(params[0])
if attribute is None:
self.show_error('no such characteristic')
return
# use write with response if supported
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()}"),
with_response = (
(attribute.properties & Characteristic.WRITE)
if hasattr(attribute, "properties")
else False
)
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()
await self.connected_peer.write_value(
attribute, value, with_response=with_response
)
async def do_exit(self, params):
self.ui.exit()
-3
View File
@@ -123,9 +123,6 @@ 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)
+3 -38
View File
@@ -110,9 +110,6 @@ 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)})'
@@ -547,36 +544,10 @@ class Client:
for subscriber_set in subscriber_sets:
if subscriber is not None:
subscriber_set.add(subscriber)
# 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)
subscriber_set.add(lambda value: characteristic.emit('update', self.connection, value))
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
@@ -743,10 +714,7 @@ class Client:
if not subscribers:
logger.warning('!!! received notification with no subscriber')
for subscriber in subscribers:
if callable(subscriber):
subscriber(notification.attribute_value)
else:
subscriber.emit('update', notification.attribute_value)
subscriber(notification.attribute_value)
def on_att_handle_value_indication(self, indication):
# Call all subscribers
@@ -754,10 +722,7 @@ class Client:
if not subscribers:
logger.warning('!!! received indication with no subscriber')
for subscriber in subscribers:
if callable(subscriber):
subscriber(indication.attribute_value)
else:
subscriber.emit('update', indication.attribute_value)
subscriber(indication.attribute_value)
# Confirm that we received the indication
self.send_confirmation(ATT_Handle_Value_Confirmation())
+17 -48
View File
@@ -419,12 +419,10 @@ async def test_subscribe_notify():
assert(len(c) == 1)
c3 = c[0]
c1._called = False
c1._last_update = None
def on_c1_update(value):
c1._called = True
c1._last_update = value
def on_c1_update(connection, value):
c1._last_update = (connection, value)
c1.on('update', on_c1_update)
await peer.subscribe(c1)
@@ -436,73 +434,44 @@ async def test_subscribe_notify():
assert(not characteristic1._last_subscription[2])
await server.indicate_subscribers(characteristic1)
await async_barrier()
assert(not c1._called)
assert(c1._last_update is None)
await server.notify_subscribers(characteristic1)
await async_barrier()
assert(c1._called)
assert(c1._last_update == characteristic1.value)
assert(c1._last_update is not None)
assert(c1._last_update[1] == 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._called = True
c2._last_update = value
c2._last_update = (connection, value)
await peer.subscribe(c2, on_c2_update)
await async_barrier()
await server.notify_subscriber(characteristic2._last_subscription[0], characteristic2)
await async_barrier()
assert(not c2._called)
assert(c2._last_update is None)
await server.indicate_subscriber(characteristic2._last_subscription[0], characteristic2)
await async_barrier()
assert(c2._called)
assert(c2._last_update == characteristic2.value)
assert(c2._last_update is not None)
assert(c2._last_update[1] == characteristic2.value)
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)
c3._last_update = None
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
def on_c3_update(connection, value):
c3._last_update = (connection, value)
c3.on('update', on_c3_update)
await peer.subscribe(c3, on_c3_update_2)
await peer.subscribe(c3)
await async_barrier()
await server.notify_subscriber(characteristic3._last_subscription[0], characteristic3)
await async_barrier()
assert(c3._called)
assert(c3._last_update == characteristic3.value)
assert(c3._called_2)
assert(c3._last_update_2 == characteristic3.value)
assert(c3._last_update is not None)
assert(c3._last_update[1] == characteristic3.value)
characteristic3.value = bytes([1, 2, 3])
await server.indicate_subscriber(characteristic3._last_subscription[0], characteristic3)
await async_barrier()
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)
assert(c3._last_update is not None)
assert(c3._last_update[1] == characteristic3.value)
# -----------------------------------------------------------------------------