From 4d6f39b2def49f9f90754781ff56c8e15bcc37fb Mon Sep 17 00:00:00 2001 From: pstruebi Date: Tue, 4 Mar 2025 15:50:49 +0100 Subject: [PATCH 1/5] refractoring of the config --- src/auracast/auracast_config.py | 86 +++++++++++++++---------------- src/auracast/multicast.py | 17 +++--- src/auracast/multicast_control.py | 6 +-- src/auracast/multicast_server.py | 6 +-- 4 files changed, 54 insertions(+), 61 deletions(-) diff --git a/src/auracast/auracast_config.py b/src/auracast/auracast_config.py index 63bc690..f1b2b03 100644 --- a/src/auracast/auracast_config.py +++ b/src/auracast/auracast_config.py @@ -43,10 +43,9 @@ global_base_config = AuracastGlobalConfig(qos_config=AuracastQoSConfig()) # "'stdin' -> receive audio from stdin as int16 PCM, " # "'file: -> read audio from a .wav or raw int16 PCM file. " - @dataclass class AuracastBigConfig: - id: int = 123456, + id: int = 123456 random_address: hci.Address = hci.Address('F1:F1:F2:F3:F4:F5') code: str = None # Broadcast_Code – a 16-octet parameter provided by the Host language: str = 'eng' # See: https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes @@ -58,49 +57,48 @@ class AuracastBigConfig: precode_wav: bool = False iso_que_len: int = 64 +# example configurations with inherit +@dataclass +class AuracastBigConfigDe(AuracastBigConfig): + id: int = 12 + random_address: hci.Address=hci.Address('F1:F1:F2:F3:F4:F5') + name: str = 'Broadcast0' + language: str ='deu' + program_info: str = 'Announcements German' + audio_source: str = 'file:./testdata/announcement_de.wav' -# Instanciate some example configurations -broadcast_de = AuracastBigConfig( - id=12, - random_address=hci.Address('F1:F1:F2:F3:F4:F5'), - name = 'Broadcast0', - language='deu', - program_info = 'Announcements German', - audio_source = 'file:./testdata/announcement_de.wav', -) +@dataclass +class AuracastBigConfigEn(AuracastBigConfig): + id: int = 123 + random_address: str =hci.Address('F2:F1:F2:F3:F4:F5') + name: str = 'Broadcast1' + language: str ='eng' + program_info: str = 'Announcements English' + audio_source: str = 'file:./testdata/announcement_en.wav' -broadcast_en = AuracastBigConfig( - id=123, - random_address=hci.Address('F2:F1:F2:F3:F4:F5'), - name = 'Broadcast1', - language='eng', - program_info = 'Announcements English', - audio_source = 'file:./testdata/announcement_en.wav', -) +@dataclass +class AuracastBigConfigFr(AuracastBigConfig): + id: int =1234 + random_address: str =hci.Address('F3:F1:F2:F3:F4:F5') + name: str = 'Broadcast2' + language: str ='fra' + program_info: str = 'Announcements French' + audio_source: str = 'file:./testdata/announcement_fr.wav' -broadcast_fr = AuracastBigConfig( - id=1234, - random_address=hci.Address('F3:F1:F2:F3:F4:F5'), - name = 'Broadcast2', - language='fra', - program_info = 'Announcements French', - audio_source = 'file:./testdata/announcement_fr.wav', -) +@dataclass +class AuracastBigConfigEs(AuracastBigConfig): + id: int =12345 + random_address: str =hci.Address('F4:F1:F2:F3:F4:F5') + name: str = 'Broadcast3' + language: str ='spa' + program_info: str = 'Announcements Spanish' + audio_source: str = 'file:./testdata/announcement_es.wav' -broadcast_es = AuracastBigConfig( - id=12345, - random_address=hci.Address('F4:F1:F2:F3:F4:F5'), - name = 'Broadcast3', - language='spa', - program_info = 'Announcements Spanish', - audio_source = 'file:./testdata/announcement_es.wav', -) - -broadcast_it = AuracastBigConfig( - id=123456, - random_address=hci.Address('F5:F1:F2:F3:F4:F5'), - name = 'Broadcast4', - language='ita', - program_info = 'Announcements Italian', - audio_source = 'file:./testdata/announcement_it.wav', -) \ No newline at end of file +@dataclass +class AuracastBigConfigIt(AuracastBigConfig): + id: int =1234567 + random_address: str =hci.Address('F5:F1:F2:F3:F4:F5') + name: str = 'Broadcast4' + language: str ='ita' + program_info: str = 'Announcements Italian' + audio_source: str = 'file:./testdata/announcement_it.wav' diff --git a/src/auracast/multicast.py b/src/auracast/multicast.py index d1ce5de..e4472e5 100644 --- a/src/auracast/multicast.py +++ b/src/auracast/multicast.py @@ -496,13 +496,8 @@ async def broadcast(global_conf: auracast_config.AuracastGlobalConfig, big_conf: if __name__ == "__main__": import os - if os.environ['LOG_LEVEL']: - log_level = getattr(logging, os.environ['LOG_LEVEL']) - else: - log_level = logging.DEBUG - log_level = os.environ['LOG_LEVEL'] logging.basicConfig( - level=log_level, + level=os.environ.get('LOG_LEVEL', logging.DEBUG), format='%(module)s.py:%(lineno)d %(levelname)s: %(message)s' ) os.chdir(os.path.dirname(__file__)) @@ -525,11 +520,11 @@ if __name__ == "__main__": global_conf.qos_config = auracast_config.qos_config_mono_high_rel bigs = [ - auracast_config.broadcast_de, - auracast_config.broadcast_en, - auracast_config.broadcast_fr, - #auracast_config.broadcast_es, - #auracast_config.broadcast_it, + auracast_config.AuracastBigConfigDe(), + auracast_config.AuracastBigConfigEn(), + auracast_config.AuracastBigConfigFr(), + #auracast_config.AuracastBigConfigEs(), + #auracast_config.AuracastBigConfigIt(), ] for big in bigs: # TODO: encrypted streams are not working #big.code = 'ff'*16 # returns hci/HCI_ENCRYPTION_MODE_NOT_ACCEPTABLE_ERROR diff --git a/src/auracast/multicast_control.py b/src/auracast/multicast_control.py index 8bc3e4d..1e9e5ab 100644 --- a/src/auracast/multicast_control.py +++ b/src/auracast/multicast_control.py @@ -123,9 +123,9 @@ async def main(): global_conf.transport='serial:/dev/serial/by-id/usb-ZEPHYR_Zephyr_HCI_UART_sample_81BD14B8D71B5662-if00,115200,rtscts' #nrf52dongle hci_uart usb cdc big_conf = [ - auracast_config.broadcast_de, - auracast_config.broadcast_en, - auracast_config.broadcast_fr, + auracast_config.AuracastBigConfigDe(), + auracast_config.AuracastBigConfigEn(), + auracast_config.AuracastBigConfigFr(), #auracast_config.broadcast_es, #auracast_config.broadcast_it, ] diff --git a/src/auracast/multicast_server.py b/src/auracast/multicast_server.py index 05849ee..866b5c6 100644 --- a/src/auracast/multicast_server.py +++ b/src/auracast/multicast_server.py @@ -11,9 +11,9 @@ global_conf = auracast_config.global_base_config global_conf.transport='serial:/dev/serial/by-id/usb-ZEPHYR_Zephyr_HCI_UART_sample_81BD14B8D71B5662-if00,115200,rtscts' #nrf52dongle hci_uart usb cdc big_conf = { # TODO: use another dataclass for this to be able to iterate over the names - 'broadcast_de': auracast_config.broadcast_de, - 'broadcast_en': auracast_config.broadcast_en, - 'broadcast_fr': auracast_config.broadcast_fr, + 'broadcast_de': auracast_config.AuracastBigConfigDe(), + 'broadcast_en': auracast_config.AuracastBigConfigEn(), + 'broadcast_fr': auracast_config.AuracastBigConfigFr(), #auracast_config.broadcast_es, #auracast_config.broadcast_it, } -- 2.52.0 From e1096d5407a14094b29b49e325ac72998b135901 Mon Sep 17 00:00:00 2001 From: pstruebi Date: Wed, 5 Mar 2025 11:57:18 +0100 Subject: [PATCH 2/5] make use of pydantic for configuration management --- pyproject.toml | 1 + src/auracast/auracast_config.py | 32 ++++++++++++-------------------- src/auracast/multicast.py | 4 ++-- 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0fbc98b..8632d7c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,6 +9,7 @@ dependencies = [ "sounddevice", "aioconsole", "quart == 0.20.0", + "pydantic" ] [project.optional-dependencies] diff --git a/src/auracast/auracast_config.py b/src/auracast/auracast_config.py index f1b2b03..c325236 100644 --- a/src/auracast/auracast_config.py +++ b/src/auracast/auracast_config.py @@ -1,10 +1,10 @@ from bumble import hci from bumble.profiles import bap from dataclasses import dataclass +from pydantic import BaseModel -# Define some base dataclasses to hold the relevant parameters -@dataclass -class AuracastQoSConfig: +# Define some base to hold the relevant parameters +class AuracastQoSConfig(BaseModel): iso_int_multiple_10ms: int = 1 number_of_retransmissions:int = 4 #4 max_transport_latency_ms:int = 43 #varies from the default value in bumble (was 65) @@ -21,13 +21,12 @@ qos_config_mono_low_rel = AuracastQoSConfig( #highest latency max_transport_latency_ms = 65 ) -@dataclass -class AuracastGlobalConfig: +class AuracastGlobalConfig(BaseModel): qos_config: AuracastQoSConfig debug: bool = False device_name: str = 'Auracaster' transport: str = '' - auracast_device_address: hci.Address = hci.Address('F0:F1:F2:F3:F4:F5') + auracast_device_address: str = 'F0:F1:F2:F3:F4:F5' auracast_sampling_rate_hz: int = 16000 octets_per_frame: int = 40 #48kbps@24kHz # bitrate = octets_per_frame * 8 / frame len frame_duration_us: int = 10000 @@ -43,10 +42,9 @@ global_base_config = AuracastGlobalConfig(qos_config=AuracastQoSConfig()) # "'stdin' -> receive audio from stdin as int16 PCM, " # "'file: -> read audio from a .wav or raw int16 PCM file. " -@dataclass -class AuracastBigConfig: +class AuracastBigConfig(BaseModel): id: int = 123456 - random_address: hci.Address = hci.Address('F1:F1:F2:F3:F4:F5') + random_address: str = 'F1:F1:F2:F3:F4:F5' code: str = None # Broadcast_Code – a 16-octet parameter provided by the Host language: str = 'eng' # See: https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes name: str = 'Broadcast0' @@ -57,47 +55,41 @@ class AuracastBigConfig: precode_wav: bool = False iso_que_len: int = 64 -# example configurations with inherit -@dataclass class AuracastBigConfigDe(AuracastBigConfig): id: int = 12 - random_address: hci.Address=hci.Address('F1:F1:F2:F3:F4:F5') + random_address: str = 'F1:F1:F2:F3:F4:F5' name: str = 'Broadcast0' language: str ='deu' program_info: str = 'Announcements German' audio_source: str = 'file:./testdata/announcement_de.wav' -@dataclass class AuracastBigConfigEn(AuracastBigConfig): id: int = 123 - random_address: str =hci.Address('F2:F1:F2:F3:F4:F5') + random_address: str = 'F2:F1:F2:F3:F4:F5' name: str = 'Broadcast1' language: str ='eng' program_info: str = 'Announcements English' audio_source: str = 'file:./testdata/announcement_en.wav' -@dataclass class AuracastBigConfigFr(AuracastBigConfig): id: int =1234 - random_address: str =hci.Address('F3:F1:F2:F3:F4:F5') + random_address: str = 'F3:F1:F2:F3:F4:F5' name: str = 'Broadcast2' language: str ='fra' program_info: str = 'Announcements French' audio_source: str = 'file:./testdata/announcement_fr.wav' -@dataclass class AuracastBigConfigEs(AuracastBigConfig): id: int =12345 - random_address: str =hci.Address('F4:F1:F2:F3:F4:F5') + random_address: str = 'F4:F1:F2:F3:F4:F5' name: str = 'Broadcast3' language: str ='spa' program_info: str = 'Announcements Spanish' audio_source: str = 'file:./testdata/announcement_es.wav' -@dataclass class AuracastBigConfigIt(AuracastBigConfig): id: int =1234567 - random_address: str =hci.Address('F5:F1:F2:F3:F4:F5') + random_address: str = 'F5:F1:F2:F3:F4:F5' name: str = 'Broadcast4' language: str ='ita' program_info: str = 'Announcements Italian' diff --git a/src/auracast/multicast.py b/src/auracast/multicast.py index e4472e5..ac13a31 100644 --- a/src/auracast/multicast.py +++ b/src/auracast/multicast.py @@ -108,7 +108,7 @@ async def create_device(config: auracast_config.AuracastGlobalConfig) -> AsyncGe ): device_config = bumble.device.DeviceConfiguration( name=config.device_name, - address=config.auracast_device_address, + address= hci.Address(config.auracast_device_address), keystore='JsonKeyStore', #le_simultaneous_enabled=True #TODO: What is this doing ? ) @@ -201,7 +201,7 @@ async def init_broadcast( ) bigs[f'big{i}']['broadcast_audio_announcement'] = bap.BroadcastAudioAnnouncement(conf.id) advertising_set = await device.create_advertising_set( - random_address=conf.random_address, + random_address=hci.Address(conf.random_address), advertising_parameters=bumble.device.AdvertisingParameters( advertising_event_properties=bumble.device.AdvertisingEventProperties( is_connectable=False -- 2.52.0 From 3715c86827ddf44ab94f755e424bcdd01a8b7090 Mon Sep 17 00:00:00 2001 From: pstruebi Date: Wed, 5 Mar 2025 14:15:58 +0100 Subject: [PATCH 3/5] Implement config with pydantic --- src/auracast/auracast_config.py | 40 ++++++++++++++++--------------- src/auracast/multicast.py | 6 ++--- src/auracast/multicast_control.py | 9 +++++-- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/src/auracast/auracast_config.py b/src/auracast/auracast_config.py index c325236..cce0f9b 100644 --- a/src/auracast/auracast_config.py +++ b/src/auracast/auracast_config.py @@ -1,25 +1,26 @@ -from bumble import hci -from bumble.profiles import bap -from dataclasses import dataclass from pydantic import BaseModel # Define some base to hold the relevant parameters class AuracastQoSConfig(BaseModel): + iso_int_multiple_10ms: int + number_of_retransmissions: int + max_transport_latency_ms: int + +class AuracastQosHigh(AuracastQoSConfig): iso_int_multiple_10ms: int = 1 number_of_retransmissions:int = 4 #4 max_transport_latency_ms:int = 43 #varies from the default value in bumble (was 65) -qos_config_mono_high_rel = AuracastQoSConfig() #highest rel + lowest latency -qos_config_mono_medium_rel = AuracastQoSConfig( - iso_int_multiple_10ms = 2, - number_of_retransmissions = 3, - max_transport_latency_ms = 65 -) -qos_config_mono_low_rel = AuracastQoSConfig( #highest latency - iso_int_multiple_10ms = 3, - number_of_retransmissions = 2, - max_transport_latency_ms = 65 -) +class AuracastQosMid(AuracastQoSConfig): + iso_int_multiple_10ms: int = 2 + number_of_retransmissions:int = 3 + max_transport_latency_ms:int = 65 + +class AuracastQosLow(AuracastQoSConfig): + iso_int_multiple_10ms: int = 3 + number_of_retransmissions:int = 2 #4 + max_transport_latency_ms:int = 65 #varies from the default value in bumble (was 65) + class AuracastGlobalConfig(BaseModel): qos_config: AuracastQoSConfig @@ -31,9 +32,8 @@ class AuracastGlobalConfig(BaseModel): octets_per_frame: int = 40 #48kbps@24kHz # bitrate = octets_per_frame * 8 / frame len frame_duration_us: int = 10000 presentation_delay_us: int = 40000 - manufacturer_data: tuple[int, bytes] = None + manufacturer_data: tuple[int, bytes] |None = None # TODO:pydantic does not support bytes serialization -global_base_config = AuracastGlobalConfig(qos_config=AuracastQoSConfig()) # "Audio input. " # "'device' -> use the host's default sound input device, " @@ -41,11 +41,10 @@ global_base_config = AuracastGlobalConfig(qos_config=AuracastQoSConfig()) # "(specify 'device:?' to get a list of available sound input devices), " # "'stdin' -> receive audio from stdin as int16 PCM, " # "'file: -> read audio from a .wav or raw int16 PCM file. " - class AuracastBigConfig(BaseModel): id: int = 123456 random_address: str = 'F1:F1:F2:F3:F4:F5' - code: str = None # Broadcast_Code – a 16-octet parameter provided by the Host + code: str | None = None # Broadcast_Code – a 16-octet parameter provided by the Host language: str = 'eng' # See: https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes name: str = 'Broadcast0' program_info: str = 'Some Announcements' @@ -72,7 +71,7 @@ class AuracastBigConfigEn(AuracastBigConfig): audio_source: str = 'file:./testdata/announcement_en.wav' class AuracastBigConfigFr(AuracastBigConfig): - id: int =1234 + id: int = 1234 random_address: str = 'F3:F1:F2:F3:F4:F5' name: str = 'Broadcast2' language: str ='fra' @@ -94,3 +93,6 @@ class AuracastBigConfigIt(AuracastBigConfig): language: str ='ita' program_info: str = 'Announcements Italian' audio_source: str = 'file:./testdata/announcement_it.wav' + + +# TODO: could be best to merge all in just one CONFIG class and give every language an enable parameter \ No newline at end of file diff --git a/src/auracast/multicast.py b/src/auracast/multicast.py index ac13a31..cbfd973 100644 --- a/src/auracast/multicast.py +++ b/src/auracast/multicast.py @@ -502,7 +502,9 @@ if __name__ == "__main__": ) os.chdir(os.path.dirname(__file__)) - global_conf = auracast_config.global_base_config + global_conf = auracast_config.AuracastGlobalConfig( + qos_config=auracast_config.AuracastQosHigh() + ) #global_conf.transport='serial:/dev/serial/by-id/usb-ZEPHYR_Zephyr_HCI_UART_sample_81BD14B8D71B5662-if00,1000000,rtscts' # transport for nrf52 dongle @@ -516,8 +518,6 @@ if __name__ == "__main__": # TODO: How can we use other iso interval than 10ms ?(medium or low rel) ? - nrf53audio receiver repports I2S tx underrun - #global_conf.qos_config = auracast_config.qos_config_mono_medium_rel - global_conf.qos_config = auracast_config.qos_config_mono_high_rel bigs = [ auracast_config.AuracastBigConfigDe(), diff --git a/src/auracast/multicast_control.py b/src/auracast/multicast_control.py index 1e9e5ab..094268e 100644 --- a/src/auracast/multicast_control.py +++ b/src/auracast/multicast_control.py @@ -113,12 +113,17 @@ async def command_line_ui(caster: Multicaster): print("Invalid command.") async def main(): + import os + logging.basicConfig( - level=logging.DEBUG, + level=os.environ.get('LOG_LEVEL', logging.DEBUG), format='%(module)s.py:%(lineno)d %(levelname)s: %(message)s' ) + os.chdir(os.path.dirname(__file__)) - global_conf = auracast_config.global_base_config + global_conf = auracast_config.AuracastGlobalConfig( + qos_config=auracast_config.AuracastQosHigh() + ) #global_conf.transport='serial:/dev/serial/by-id/usb-SEGGER_J-Link_001057705357-if02,1000000,rtscts' # transport for nrf54l15dk global_conf.transport='serial:/dev/serial/by-id/usb-ZEPHYR_Zephyr_HCI_UART_sample_81BD14B8D71B5662-if00,115200,rtscts' #nrf52dongle hci_uart usb cdc -- 2.52.0 From 931f1006baf64f7da84bfec2f00449c1011c5157 Mon Sep 17 00:00:00 2001 From: pstruebi Date: Wed, 5 Mar 2025 17:00:35 +0100 Subject: [PATCH 4/5] refractoring --- src/auracast/auracast_config.py | 2 +- src/auracast/multicast.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/auracast/auracast_config.py b/src/auracast/auracast_config.py index cce0f9b..ad5ac03 100644 --- a/src/auracast/auracast_config.py +++ b/src/auracast/auracast_config.py @@ -23,7 +23,7 @@ class AuracastQosLow(AuracastQoSConfig): class AuracastGlobalConfig(BaseModel): - qos_config: AuracastQoSConfig + qos_config: AuracastQoSConfig = AuracastQosHigh() debug: bool = False device_name: str = 'Auracaster' transport: str = '' diff --git a/src/auracast/multicast.py b/src/auracast/multicast.py index cbfd973..67c041c 100644 --- a/src/auracast/multicast.py +++ b/src/auracast/multicast.py @@ -496,7 +496,7 @@ async def broadcast(global_conf: auracast_config.AuracastGlobalConfig, big_conf: if __name__ == "__main__": import os - logging.basicConfig( + logging.basicConfig( #export LOG_LEVEL=INFO level=os.environ.get('LOG_LEVEL', logging.DEBUG), format='%(module)s.py:%(lineno)d %(levelname)s: %(message)s' ) -- 2.52.0 From 2083ff0599bf5a50b82ab5ad1bf29928a05509c4 Mon Sep 17 00:00:00 2001 From: pstruebi Date: Wed, 5 Mar 2025 17:54:33 +0100 Subject: [PATCH 5/5] rename a file --- src/auracast/{multicast_control_client.py => multicast_client.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/auracast/{multicast_control_client.py => multicast_client.py} (100%) diff --git a/src/auracast/multicast_control_client.py b/src/auracast/multicast_client.py similarity index 100% rename from src/auracast/multicast_control_client.py rename to src/auracast/multicast_client.py -- 2.52.0