diff --git a/bumble/device.py b/bumble/device.py index 300afd20..f9d62b6f 100644 --- a/bumble/device.py +++ b/bumble/device.py @@ -940,6 +940,12 @@ class Device(CompositeEventEmitter): if self.advertising: await self.stop_advertising() + # add each Service's advertising data + for attribute in self.gatt_server.attributes: + if isinstance(attribute, Service): + self.advertising_data += attribute.get_service_advertising_data() + + # Set/update the advertising data if the advertising type allows it if advertising_type.has_data: await self.send_command(HCI_LE_Set_Advertising_Data_Command( diff --git a/bumble/gatt.py b/bumble/gatt.py index cef42aa1..ef944fd4 100644 --- a/bumble/gatt.py +++ b/bumble/gatt.py @@ -210,6 +210,15 @@ class Service(Attribute): self.characteristics = characteristics[:] self.primary = primary + + def get_service_advertising_data(self): + """ + Get Service specific advertising data + Defined by each Service, default value is empty + """ + return b'' + + def __str__(self): return f'Service(handle=0x{self.handle:04X}, end=0x{self.end_group_handle:04X}, uuid={self.uuid}){"" if self.primary else "*"}' diff --git a/bumble/profiles/asha_service.py b/bumble/profiles/asha_service.py index 96dec633..06fae720 100644 --- a/bumble/profiles/asha_service.py +++ b/bumble/profiles/asha_service.py @@ -17,122 +17,129 @@ # Imports # ----------------------------------------------------------------------------- import struct -from ..device import ( - Device, AdvertisingType -) +import logging from ..core import AdvertisingData from ..gatt import ( - GATT_ASHA_SERVICE, - GATT_ASHA_READ_ONLY_PROPERTIES_CHARACTERISTIC, - GATT_ASHA_AUDIO_CONTROL_POINT_CHARACTERISTIC, - GATT_ASHA_AUDIO_STATUS_CHARACTERISTIC, - GATT_ASHA_VOLUME_CHARACTERISTIC, - GATT_ASHA_LE_PSM_OUT_CHARACTERISTIC, - TemplateService, - Characteristic, - CharacteristicValue, - PackedCharacteristicAdapter + GATT_ASHA_SERVICE, + GATT_ASHA_READ_ONLY_PROPERTIES_CHARACTERISTIC, + GATT_ASHA_AUDIO_CONTROL_POINT_CHARACTERISTIC, + GATT_ASHA_AUDIO_STATUS_CHARACTERISTIC, + GATT_ASHA_VOLUME_CHARACTERISTIC, + GATT_ASHA_LE_PSM_OUT_CHARACTERISTIC, + TemplateService, + Characteristic, + CharacteristicValue, + PackedCharacteristicAdapter ) +# ----------------------------------------------------------------------------- +# Logging +# ----------------------------------------------------------------------------- +logger = logging.getLogger(__name__) + # ----------------------------------------------------------------------------- class AshaService(TemplateService): - UUID = GATT_ASHA_SERVICE + UUID = GATT_ASHA_SERVICE + OPCODE_START = 1 + OPCODE_STOP = 2 + OPCODE_STATUS = 3 + PROTOCOL_VERSION = 0x01 + RESERVED_FOR_FUTURE_USE = [00, 00] + FEATURE_MAP = [0x01] # [LE CoC audio output streaming supported] + SUPPORTED_CODEDC_ID = [0x02, 0x01] # Codec IDs [G.722 at 16 kHz] - def __init__(self,device:Device): - self.device=device + def __init__(self, capability: int, hisyncid: []): + self.hisyncid = hisyncid + self.capability = capability # Device Capabilities [Left, Monaural] + self.render_delay = [00, 00] + self.reserved_for_future_use = [00, 00] - # Handler for volume control - def on_volume_write(connection, value): - print('--- VOLUME Write:', value[0]) + # Four least significant bytes of the HiSyncId. + self.truncated_hisyncid = self.hisyncid[:4] - # Handler for audio control commands - def on_audio_control_point_write(connection, value): - print('--- AUDIO CONTROL POINT Write:', value.hex()) - opcode = value[0] - if opcode == 1: - # Start - audio_type = ('Unknown', 'Ringtone', 'Phone Call', 'Media')[value[2]] - print( - f'### START: codec={value[1]}, audio_type={audio_type}, volume={value[3]}, otherstate={value[4]}') - elif opcode == 2: - print('### STOP') - elif opcode == 3: - print(f'### STATUS: connected={value[1]}') + # Handler for volume control + def on_volume_write(connection, value): + logger.info(f'--- VOLUME Write:{value[0]}') - # TODO Respond with a status - # asyncio.create_task(device.notify_subscribers(audio_status_characteristic, force=True)) + # Handler for audio control commands + def on_audio_control_point_write(connection, value): + logger.info(f'--- AUDIO CONTROL POINT Write:{value.hex()}') + opcode = value[0] + if opcode == AshaService.OPCODE_START: + # Start + audio_type = ('Unknown', 'Ringtone', 'Phone Call', 'Media')[value[2]] + logger.info( + f'### START: codec={value[1]}, audio_type={audio_type}, volume={value[3]}, otherstate={value[4]}') + elif opcode == AshaService.OPCODE_STOP: + logger.info('### STOP') + elif opcode == AshaService.OPCODE_STATUS: + logger.info(f'### STATUS: connected={value[1]}') - self.read_only_properties_characteristic = Characteristic( - GATT_ASHA_READ_ONLY_PROPERTIES_CHARACTERISTIC, - Characteristic.READ, - Characteristic.READABLE, - bytes([ - 0x01, # Version - 0x00, # Device Capabilities [Left, Monaural] - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, # HiSyncId - 0x01, # Feature Map [LE CoC audio output streaming supported] - 0x00, 0x00, # Render Delay - 0x00, 0x00, # RFU - 0x02, 0x00 # Codec IDs [G.722 at 16 kHz] - ]) - ) + # TODO Respond with a status + # asyncio.create_task(device.notify_subscribers(audio_status_characteristic, force=True)) - self.audio_control_point_characteristic = Characteristic( - GATT_ASHA_AUDIO_CONTROL_POINT_CHARACTERISTIC, - Characteristic.WRITE | Characteristic.WRITE_WITHOUT_RESPONSE, - Characteristic.WRITEABLE, - CharacteristicValue(write=on_audio_control_point_write) - ) - self.audio_status_characteristic = Characteristic( - GATT_ASHA_AUDIO_STATUS_CHARACTERISTIC, - Characteristic.READ | Characteristic.NOTIFY, - Characteristic.READABLE, - bytes([0]) - ) - self.volume_characteristic = Characteristic( - GATT_ASHA_VOLUME_CHARACTERISTIC, - Characteristic.WRITE_WITHOUT_RESPONSE, - Characteristic.WRITEABLE, - CharacteristicValue(write=on_volume_write) - ) + self.read_only_properties_characteristic = Characteristic( + GATT_ASHA_READ_ONLY_PROPERTIES_CHARACTERISTIC, + Characteristic.READ, + Characteristic.READABLE, + bytes([ + AshaService.PROTOCOL_VERSION, # Version + self.capability, + ]) + + bytes(self.hisyncid) + + bytes(AshaService.FEATURE_MAP)+ + bytes(self.render_delay)+ + bytes(AshaService.RESERVED_FOR_FUTURE_USE)+ + bytes(AshaService.SUPPORTED_CODEDC_ID) + ) - # TODO add real psm value - self.psm=0x0080 - # self.psm = device.register_l2cap_channel_server(0, on_coc, 8) - self.le_psm_out_characteristic = Characteristic( - GATT_ASHA_LE_PSM_OUT_CHARACTERISTIC, - Characteristic.READ, - Characteristic.READABLE, - struct.pack('