use fastapi for the client/server
This commit is contained in:
+2
-1
@@ -8,7 +8,8 @@ dependencies = [
|
||||
"lc3 @ git+ssh://git@ssh.pstruebi.xyz:222/auracaster/liblc3.git@7558637303106c7ea971e7bb8cedf379d3e08bcc",
|
||||
"sounddevice",
|
||||
"aioconsole",
|
||||
"quart == 0.20.0",
|
||||
"fastapi==0.115.11",
|
||||
"uvicorn==0.34.0",
|
||||
"pydantic"
|
||||
]
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from typing import List
|
||||
from pydantic import BaseModel
|
||||
|
||||
# Define some base to hold the relevant parameters
|
||||
@@ -32,8 +33,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 = None # TODO:pydantic does not support bytes serialization
|
||||
|
||||
# TODO:pydantic does not support bytes serialization - use .hex and np.fromhex()
|
||||
manufacturer_data: tuple[int, bytes] | tuple[None, None] = (None, None)
|
||||
|
||||
# "Audio input. "
|
||||
# "'device' -> use the host's default sound input device, "
|
||||
@@ -95,4 +96,10 @@ class AuracastBigConfigIt(AuracastBigConfig):
|
||||
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
|
||||
# TODO: could be best to merge all in just one CONFIG class and give every language an enable parameter
|
||||
|
||||
# TODO: Used in the client/server context, should probably be used verywhere
|
||||
class AuracastConfigGroup(AuracastGlobalConfig):
|
||||
bigs: List[AuracastBigConfig] = [
|
||||
AuracastBigConfigDe(),
|
||||
]
|
||||
|
||||
@@ -186,7 +186,7 @@ async def init_broadcast(
|
||||
logger.info('Setup Advertising')
|
||||
advertising_manufacturer_data = (
|
||||
b''
|
||||
if global_config.manufacturer_data is None
|
||||
if global_config.manufacturer_data == (None, None)
|
||||
else bytes(
|
||||
core.AdvertisingData(
|
||||
[
|
||||
|
||||
@@ -1,12 +1,30 @@
|
||||
import requests
|
||||
|
||||
from typing import List
|
||||
from auracast import auracast_config
|
||||
|
||||
from auracast.utils.read_lc3_file import read_lc3_file
|
||||
|
||||
BASE_URL = "http://127.0.0.1:5000" # Adjust based on your actual API URL
|
||||
|
||||
def initialize():
|
||||
response = requests.post(f"{BASE_URL}/init")
|
||||
# TODO: put this to a common location
|
||||
class AuracastConfigGroup(auracast_config.AuracastGlobalConfig):
|
||||
bigs: List[auracast_config.AuracastBigConfig] = [
|
||||
auracast_config.AuracastBigConfigDe(),
|
||||
]
|
||||
|
||||
|
||||
def request_init(request_data : AuracastConfigGroup):
|
||||
response = requests.post(f"{BASE_URL}/init", json=request_data.model_dump())
|
||||
if response.status_code != 200:
|
||||
raise(f"Error: {response.status_code}, {response.text}")
|
||||
|
||||
|
||||
def send_audio(data_dict):
|
||||
response = requests.post(f"{BASE_URL}/stream_lc3", json=data_dict)
|
||||
return response.json()
|
||||
|
||||
|
||||
def shutdown():
|
||||
response = requests.post(f"{BASE_URL}/shutdown")
|
||||
return response.json()
|
||||
@@ -15,22 +33,31 @@ def stop_audio():
|
||||
response = requests.post(f"{BASE_URL}/stop_audio")
|
||||
return response.json()
|
||||
|
||||
def send_audio(data_dict):
|
||||
response = requests.post(f"{BASE_URL}/stream_lc3", json=data_dict)
|
||||
return response.json()
|
||||
|
||||
def get_status():
|
||||
response = requests.get(f"{BASE_URL}/status")
|
||||
return response.json()
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_audio_data = { # TODO: investigate further whats the best way to actually transfer the data
|
||||
"broadcast_deu": read_lc3_file('src/auracast/testdata/announcement_de_10_16_32.lc3').decode('latin-1'),
|
||||
"broadcast_eng": read_lc3_file('src/auracast/testdata/announcement_en_10_16_32.lc3').decode('latin-1')
|
||||
config = AuracastConfigGroup(
|
||||
|
||||
)
|
||||
config.transport = 'serial:/dev/serial/by-id/usb-ZEPHYR_Zephyr_HCI_UART_sample_81BD14B8D71B5662-if00,115200,rtscts'
|
||||
|
||||
# Initialize language-based configurations
|
||||
for conf in config.bigs:
|
||||
conf.loop = False
|
||||
|
||||
|
||||
audio_data = { # TODO: investigate further whats the best way to actually transfer the data
|
||||
"deu": read_lc3_file('src/auracast/testdata/announcement_de_10_16_32.lc3').decode('latin-1'),
|
||||
#"eng": read_lc3_file('src/auracast/testdata/announcement_en_10_16_32.lc3').decode('latin-1'),
|
||||
#"fra": read_lc3_file('src/auracast/testdata/announcement_fr_10_16_32.lc3').decode('latin-1'),
|
||||
|
||||
}
|
||||
|
||||
print("Getting status:", get_status())
|
||||
print("Initializing server:", initialize())
|
||||
print("Initializing server:", request_init(config))
|
||||
print("Getting status:", get_status())
|
||||
print("Sending audio:", send_audio())
|
||||
print("Sending audio:", send_audio(audio_data))
|
||||
print("Getting status:", get_status())
|
||||
|
||||
@@ -1,103 +1,87 @@
|
||||
from typing import List
|
||||
import logging as log
|
||||
from dataclasses import asdict
|
||||
from quart import Quart, request, jsonify
|
||||
from auracast import multicast_control
|
||||
from auracast import auracast_config
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from auracast import multicast_control, auracast_config
|
||||
|
||||
app = Quart(__name__)
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
# TODO: redo this with fastapi, transfer whole radio config on init
|
||||
# Initialize global configuration
|
||||
global_config_group = AuracastConfigGroup()
|
||||
global_config_group.transport = 'serial:/dev/serial/by-id/usb-ZEPHYR_Zephyr_HCI_UART_sample_81BD14B8D71B5662-if00,115200,rtscts'
|
||||
|
||||
# Initialize the multicaster instance globally
|
||||
global_conf = auracast_config.AuracastGlobalConfig()
|
||||
# Create multicast controller
|
||||
multicaster: multicast_control.Multicaster | None = None
|
||||
|
||||
#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 = { # TODO: use another dataclass for this to be able to iterate over the names
|
||||
'deu': auracast_config.AuracastBigConfigDe(),
|
||||
'eng': auracast_config.AuracastBigConfigEn(),
|
||||
'fra': auracast_config.AuracastBigConfigFr(),
|
||||
#auracast_config.broadcast_es,
|
||||
#auracast_config.broadcast_it,
|
||||
}
|
||||
for conf in big_conf.values():
|
||||
conf.loop = False
|
||||
|
||||
multicaster = multicast_control.Multicaster(
|
||||
global_conf,
|
||||
list(big_conf.values()),
|
||||
)
|
||||
|
||||
@app.route('/init', methods=['POST'])
|
||||
async def initialize():
|
||||
@app.post("/init")
|
||||
async def initialize(conf: AuracastConfigGroup):
|
||||
"""Initializes the broadcasters."""
|
||||
#data = await request.json
|
||||
#global_conf = auracast_config.AuracastGlobalConfig.from_dict(data['global_config'])
|
||||
#stream_configs = [auracast_config.AuracastBigConfig.from_dict(big) for big in data['big_configs']]
|
||||
global global_config_group
|
||||
global multicaster
|
||||
try:
|
||||
# initialize the streams dict
|
||||
global_config_group = conf
|
||||
multicaster = multicast_control.Multicaster(
|
||||
conf,
|
||||
[big for big in conf.bigs],
|
||||
)
|
||||
await multicaster.init_broadcast()
|
||||
return jsonify({"status": "initialized"}), 200
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@app.route('/shutdown', methods=['POST'])
|
||||
async def stop():
|
||||
@app.post("/stream_lc3")
|
||||
async def send_audio(audio_data: dict[str, str]):
|
||||
"""Streams pre-coded LC3 audio."""
|
||||
if multicaster is None:
|
||||
raise HTTPException(status_code=500, detail='Auracast endpoint was never intialized')
|
||||
try:
|
||||
for big in global_config_group.bigs:
|
||||
assert big.language in audio_data, HTTPException(status_code=500, detail='language len missmatch')
|
||||
log.info('Received a send audio request for %s', big.language)
|
||||
big.audio_source = audio_data[big.language].encode('latin-1')
|
||||
|
||||
multicaster.big_conf = global_config_group.bigs
|
||||
multicaster.start_streaming()
|
||||
return {"status": "audio_sent"}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.post("/shutdown")
|
||||
async def shutdown():
|
||||
"""Stops broadcasting."""
|
||||
try:
|
||||
await multicaster.reset()
|
||||
return jsonify({"status": "stopped"}), 200
|
||||
return {"status": "stopped"}
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route('/stop_audio', methods=['POST'])
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.post("/stop_audio")
|
||||
async def stop_audio():
|
||||
"""Stops streaming."""
|
||||
try:
|
||||
multicaster.stop_streaming()
|
||||
return jsonify({"status": "stopped"}), 200
|
||||
return {"status": "stopped"}
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route('/stream_lc3', methods=['POST'])
|
||||
async def send_audio():
|
||||
"""Streams pre-coded LC3 audio.
|
||||
# post data in the format
|
||||
{
|
||||
broadcast_deu: b''
|
||||
broadcast_fra: b''
|
||||
|
||||
}
|
||||
"""
|
||||
post_data = await request.json
|
||||
try:
|
||||
for key, val in big_conf.items(): #TODO: loop over caster.big_conf directly
|
||||
if key in post_data:
|
||||
log.info('Received a send audio request for %s', key)
|
||||
val.audio_source = post_data[key].encode('latin-1')
|
||||
else:
|
||||
val.audio_source = b''
|
||||
|
||||
multicaster.big_conf = list(big_conf.values())
|
||||
multicaster.start_streaming()
|
||||
return jsonify({"status": "audio_sent"}), 200
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
# TODO: Also a queue should be implemented - probably as its own endpoint,
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@app.route('/status', methods=['GET'])
|
||||
@app.get("/status")
|
||||
async def get_status():
|
||||
"""Gets the current status of the multicaster."""
|
||||
status = multicaster.get_status()
|
||||
# TODO: also get queue status, announcements, samples etc.
|
||||
return jsonify({"status": status}), 200
|
||||
if multicaster:
|
||||
status = multicaster.get_status()
|
||||
return {"status": status}
|
||||
else:
|
||||
return {"status": None}
|
||||
|
||||
if __name__ == '__main__':
|
||||
import uvicorn
|
||||
log.basicConfig(
|
||||
level=log.INFO,
|
||||
format='%(module)s.py:%(lineno)d %(levelname)s: %(message)s'
|
||||
)
|
||||
app.run(host='0.0.0.0', port=5000, debug=True)
|
||||
uvicorn.run(app, host="0.0.0.0", port=5000)
|
||||
Reference in New Issue
Block a user