Implement a auracast controller module
This commit is contained in:
@@ -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
@@ -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
|
||||
|
||||
@@ -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())
|
||||
@@ -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())
|
||||
Reference in New Issue
Block a user