Implement a auracast controller module

This commit is contained in:
2025-02-23 15:10:40 +01:00
parent d43b91b633
commit 59f5d747b4
4 changed files with 153 additions and 138 deletions
+2 -2
View File
@@ -28,8 +28,8 @@ class AuracastGlobalConfig:
device_name: str = 'Auracaster'
transport: str = ''
auracast_device_address: hci.Address = hci.Address('F0:F1:F2:F3:F4:F5')
auracast_sampling_rate_hz: int = 24000
octets_per_frame: int = 60 #48kbps@24kHz # bitrate = octets_per_frame * 8 / frame len
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
presentation_delay_us: int = 40000
manufacturer_data: tuple[int, bytes] = None
+9 -12
View File
@@ -49,8 +49,7 @@ from bumble.audio import io as audio_io
import auracast_config
def chunker(b, size):
return (b[i:i+size] for i in range(0, len(b), size))
# -----------------------------------------------------------------------------
# Logging
@@ -95,7 +94,7 @@ def run_async(async_command: Coroutine) -> None:
color('!!! An error occurred while executing the command:', 'red'), message
)
async def setup_broadcast(
async def init_broadcast(
device,
global_config : auracast_config.AuracastGlobalConfig,
big_config: List[auracast_config.AuracastBigConfig]
@@ -246,7 +245,7 @@ async def setup_broadcast(
return bigs
async def setup_audio(
async def init_audio(
bigs,
global_config : auracast_config.AuracastGlobalConfig,
big_config: List[auracast_config.AuracastBigConfig]
@@ -291,8 +290,7 @@ class Streamer():
self.is_streaming = False
self.bigs = bigs
def start_streaming_task(self):
def start_streaming(self):
if not self.is_streaming:
self.task = asyncio.create_task(self.stream())
else:
@@ -325,7 +323,8 @@ class Streamer():
await big['iso_queue'].write(lc3_frame)
if all(stream_finished): # Take into account that multiple files have different lengths
print('All streams finished, stopping streamer')
logging.info('All streams finished, stopping streamer')
self.is_streaming = True
break
@@ -340,18 +339,18 @@ async def broadcast(global_conf: auracast_config.AuracastGlobalConfig, big_conf:
logger.error(color('Periodic advertising not supported', 'red'))
return
bigs = await setup_broadcast( # the bigs dictionary contains all the global configurations
bigs = await init_broadcast( # the bigs dictionary contains all the global configurations
device,
global_conf,
big_conf
)
await setup_audio(
await init_audio(
bigs,
global_conf,
big_conf
)
streamer = Streamer(bigs)
streamer.start_streaming_task()
streamer.start_streaming()
await asyncio.wait([streamer.task])
@@ -413,5 +412,3 @@ if __name__ == "__main__":
# realtime audio locally
# realtime audio network lc3 coded
# (realtime audio network uncoded)
# TODO: add support for playing new files will keeping the advertising running
-124
View File
@@ -1,124 +0,0 @@
import logging
from typing import cast, Any, AsyncGenerator, Coroutine, Dict, Optional, Tuple, List
import bumble
import bumble.device
import bumble.transport
import bumble.utils
import asyncio
import aioconsole
import multicast
import auracast_config
class Multicaster:
def __init__(self, global_conf: auracast_config.AuracastGlobalConfig, big_conf: List[auracast_config.AuracastBigConfig]):
self.running = False
self.task = None # Holds the background print task
self.global_conf = global_conf
self.big_conf = big_conf
self.device = None
self.bigs = None
async def setup_broadcast(self):
self.device_acm = multicast.create_device(self.global_conf)
agen = self.device_acm.__aenter__() # Manually triggering setup
device = await agen
self.bigs = await multicast.setup_broadcast( # the bigs dictionary contains all the global configurations
device,
self.global_conf,
self.big_conf
)
self.device = device
async def setup_audio(self):
await multicast.setup_audio(
self.bigs,
self.global_conf,
self.big_conf
)
async def stream_audio(self):
await multicast.streamer(
self.bigs
)
def start(self):
"""Starts the background task if it's not already running."""
if not self.running:
self.running = True
self.task = asyncio.create_task(self._printer_coroutine())
def stop(self):
"""Stops the background task if running."""
if self.running:
self.running = False
if self.task:
self.task.cancel() # Cancel the task safely
self.task = None
def set_message(self, new_message):
"""Updates the message being printed."""
self.message = new_message
async def __del__(self):
await self.device_acm.__aexit__(None, None, None) # Manually triggering teardown
async def command_line_ui(printer):
while True:
command = await aioconsole.ainput("\nCommands: [start|stop|set|quit] > ")
if command.strip().lower() == "start":
printer.start()
print("💡 Printer started!")
elif command.strip().lower() == "stop":
printer.stop()
print("🛑 Printer stopped!")
elif command.strip().lower().startswith("set "):
_, new_message = command.split(" ", 1)
printer.set_message(new_message)
print(f"✏️ Message updated to: {new_message}")
elif command.strip().lower() == "quit":
printer.stop()
print("👋 Exiting...")
break # Exit loop
else:
print("❌ Invalid command. Use [start|stop|set <message>|quit]")
async def main():
logging.basicConfig(
level=logging.DEBUG,
format='%(module)s.py:%(lineno)d %(levelname)s: %(message)s'
)
global_conf = auracast_config.global_base_config
#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
big_conf = [
auracast_config.broadcast_de,
auracast_config.broadcast_en,
auracast_config.broadcast_fr,
#auracast_config.broadcast_es,
#auracast_config.broadcast_it,
]
# look into:
#async with MyAPI() as api:
#pass
caster = Multicaster(global_conf, big_conf)
await caster.setup_broadcast()
#await command_line_ui(caster)
# Run the application
asyncio.run(main())
+142
View File
@@ -0,0 +1,142 @@
import logging
from typing import cast, Any, AsyncGenerator, Coroutine, Dict, Optional, Tuple, List
import bumble
import bumble.device
import bumble.transport
import bumble.utils
import asyncio
import aioconsole
import multicast
import auracast_config
class Multicaster:
def __init__(
self,
global_conf: auracast_config.AuracastGlobalConfig,
big_conf: List[auracast_config.AuracastBigConfig]
):
self.is_auracast_init = False
self.is_audio_init = False
self.streaming = False
self.task = None # Holds the background print task
self.global_conf = global_conf
self.big_conf = big_conf
self.device = None
self.bigs = None
self.streamer=None
async def init_broadcast(self):
self.device_acm = multicast.create_device(self.global_conf)
agen = self.device_acm.__aenter__() # Manually triggering setup
device = await agen
self.bigs = await multicast.init_broadcast( # the bigs dictionary contains all the global configurations
device,
self.global_conf,
self.big_conf
)
self.device = device
self.is_auracast_init = True
async def init_audio(self):
await multicast.init_audio(
self.bigs,
self.global_conf,
self.big_conf
)
self.is_audio_init = True
self.streamer = multicast.Streamer(self.bigs)
def start_streaming(self):
self.streamer.start_streaming()
def stop_streaming(self):
self.streamer.stop_streaming()
async def reset(self):
await self.shutdown() # Manually triggering teardown
self.__init__(self.global_conf, self.big_conf)
async def shutdown(self):
await self.device.stop_advertising()
for big in self.bigs.values():
await big['advertising_set'].stop()
await self.device_acm.__aexit__(None, None, None) # Manually triggering teardown
# example commandline ui
async def command_line_ui(caster: Multicaster):
while True:
command = await aioconsole.ainput("\nCommands: [start|stop|init|quit] > ")
if command.strip().lower() == "start_audio":
caster.start_streaming()
print("Audio started!")
elif command.strip().lower() == "stop_audio":
caster.stop_streaming()
print("Audio stopped!")
elif command.strip().lower() == "stop":
print("👋 Stopping...")
caster.stop_streaming()
await caster.reset()
elif command.strip().lower() == "init":
await caster.reset()
await caster.init_broadcast()
await caster.init_audio()
elif command.strip().lower() == "init_audio":
await caster.init_audio()
#elif command.strip().lower().startswith("set "):
#_, new_message = command.split(" ", 1)
#multicaster.set_message(new_message)
#print(f"✏️ Message updated to: {new_message}")
elif command.strip().lower() == "quit":
print("👋 Exiting...")
if caster.device:
caster.stop_streaming()
await caster.shutdown()
break # Exit loop
else:
print("Invalid command.")
async def main():
logging.basicConfig(
level=logging.DEBUG,
format='%(module)s.py:%(lineno)d %(levelname)s: %(message)s'
)
global_conf = auracast_config.global_base_config
#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
big_conf = [
auracast_config.broadcast_de,
auracast_config.broadcast_en,
auracast_config.broadcast_fr,
#auracast_config.broadcast_es,
#auracast_config.broadcast_it,
]
for conf in big_conf:
conf.loop_wav = False
# look into:
#async with MyAPI() as api:
#pass
caster = Multicaster(global_conf, big_conf)
await caster.init_broadcast()
await caster.init_audio()
await command_line_ui(caster)
# Run the application
asyncio.run(main())