add ASHA advertising factory method

This commit is contained in:
Yuyang Huang
2022-12-01 10:40:30 -08:00
parent d92b7e9b74
commit 87aa4f617e
3 changed files with 122 additions and 100 deletions

View File

@@ -940,6 +940,12 @@ class Device(CompositeEventEmitter):
if self.advertising: if self.advertising:
await self.stop_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 # Set/update the advertising data if the advertising type allows it
if advertising_type.has_data: if advertising_type.has_data:
await self.send_command(HCI_LE_Set_Advertising_Data_Command( await self.send_command(HCI_LE_Set_Advertising_Data_Command(

View File

@@ -210,6 +210,15 @@ class Service(Attribute):
self.characteristics = characteristics[:] self.characteristics = characteristics[:]
self.primary = primary 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): 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 "*"}' return f'Service(handle=0x{self.handle:04X}, end=0x{self.end_group_handle:04X}, uuid={self.uuid}){"" if self.primary else "*"}'

View File

@@ -17,9 +17,7 @@
# Imports # Imports
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
import struct import struct
from ..device import ( import logging
Device, AdvertisingType
)
from ..core import AdvertisingData from ..core import AdvertisingData
from ..gatt import ( from ..gatt import (
GATT_ASHA_SERVICE, GATT_ASHA_SERVICE,
@@ -34,31 +32,49 @@ from ..gatt import (
PackedCharacteristicAdapter PackedCharacteristicAdapter
) )
# -----------------------------------------------------------------------------
# Logging
# -----------------------------------------------------------------------------
logger = logging.getLogger(__name__)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
class AshaService(TemplateService): 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): def __init__(self, capability: int, hisyncid: []):
self.device=device self.hisyncid = hisyncid
self.capability = capability # Device Capabilities [Left, Monaural]
self.render_delay = [00, 00]
self.reserved_for_future_use = [00, 00]
# Four least significant bytes of the HiSyncId.
self.truncated_hisyncid = self.hisyncid[:4]
# Handler for volume control # Handler for volume control
def on_volume_write(connection, value): def on_volume_write(connection, value):
print('--- VOLUME Write:', value[0]) logger.info(f'--- VOLUME Write:{value[0]}')
# Handler for audio control commands # Handler for audio control commands
def on_audio_control_point_write(connection, value): def on_audio_control_point_write(connection, value):
print('--- AUDIO CONTROL POINT Write:', value.hex()) logger.info(f'--- AUDIO CONTROL POINT Write:{value.hex()}')
opcode = value[0] opcode = value[0]
if opcode == 1: if opcode == AshaService.OPCODE_START:
# Start # Start
audio_type = ('Unknown', 'Ringtone', 'Phone Call', 'Media')[value[2]] audio_type = ('Unknown', 'Ringtone', 'Phone Call', 'Media')[value[2]]
print( logger.info(
f'### START: codec={value[1]}, audio_type={audio_type}, volume={value[3]}, otherstate={value[4]}') f'### START: codec={value[1]}, audio_type={audio_type}, volume={value[3]}, otherstate={value[4]}')
elif opcode == 2: elif opcode == AshaService.OPCODE_STOP:
print('### STOP') logger.info('### STOP')
elif opcode == 3: elif opcode == AshaService.OPCODE_STATUS:
print(f'### STATUS: connected={value[1]}') logger.info(f'### STATUS: connected={value[1]}')
# TODO Respond with a status # TODO Respond with a status
# asyncio.create_task(device.notify_subscribers(audio_status_characteristic, force=True)) # asyncio.create_task(device.notify_subscribers(audio_status_characteristic, force=True))
@@ -68,14 +84,14 @@ class AshaService(TemplateService):
Characteristic.READ, Characteristic.READ,
Characteristic.READABLE, Characteristic.READABLE,
bytes([ bytes([
0x01, # Version AshaService.PROTOCOL_VERSION, # Version
0x00, # Device Capabilities [Left, Monaural] self.capability,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, # HiSyncId ]) +
0x01, # Feature Map [LE CoC audio output streaming supported] bytes(self.hisyncid) +
0x00, 0x00, # Render Delay bytes(AshaService.FEATURE_MAP)+
0x00, 0x00, # RFU bytes(self.render_delay)+
0x02, 0x00 # Codec IDs [G.722 at 16 kHz] bytes(AshaService.RESERVED_FOR_FUTURE_USE)+
]) bytes(AshaService.SUPPORTED_CODEDC_ID)
) )
self.audio_control_point_characteristic = Characteristic( self.audio_control_point_characteristic = Characteristic(
@@ -98,7 +114,7 @@ class AshaService(TemplateService):
) )
# TODO add real psm value # TODO add real psm value
self.psm=0x0080 self.psm = 0x0080
# self.psm = device.register_l2cap_channel_server(0, on_coc, 8) # self.psm = device.register_l2cap_channel_server(0, on_coc, 8)
self.le_psm_out_characteristic = Characteristic( self.le_psm_out_characteristic = Characteristic(
GATT_ASHA_LE_PSM_OUT_CHARACTERISTIC, GATT_ASHA_LE_PSM_OUT_CHARACTERISTIC,
@@ -115,24 +131,15 @@ class AshaService(TemplateService):
super().__init__(characteristics) super().__init__(characteristics)
async def start_advertising(self, capability: int,
truncated_hisyncid: []): def get_service_advertising_data(self):
assert self.device return bytes(
self.device.advertising_data = bytes(
AdvertisingData([ AdvertisingData([
(AdvertisingData.COMPLETE_LOCAL_NAME, bytes(self.device.name, 'utf-8')),
(AdvertisingData.FLAGS, bytes([0x06])),
(AdvertisingData.INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS, bytes(GATT_ASHA_SERVICE)), (AdvertisingData.INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS, bytes(GATT_ASHA_SERVICE)),
(AdvertisingData.SERVICE_DATA_16_BIT_UUID, bytes(GATT_ASHA_SERVICE) + bytes([ (AdvertisingData.SERVICE_DATA_16_BIT_UUID, bytes(GATT_ASHA_SERVICE) + bytes([
0x01, # Protocol Version AshaService.PROTOCOL_VERSION,
capability, # Capability self.capability,
]) + bytes(truncated_hisyncid)) ]) + bytes(self.truncated_hisyncid))
]) ])
) )
# TODO enable more advertising_type and own_address_type
advertising_type = AdvertisingType.UNDIRECTED_CONNECTABLE_SCANNABLE
await self.device.start_advertising(
advertising_type=advertising_type
)