Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move API Endpoints to subfolders #889

Merged
merged 2 commits into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file added freedata_server/api/__init__.py
Empty file.
27 changes: 27 additions & 0 deletions freedata_server/api/command_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# api/command_helpers.py
import asyncio


async def enqueue_tx_command(app, cmd_class, params={}):
"""
Enqueue a transmit command using the app's managers.

Args:
app: The FastAPI app instance (e.g., request.app) containing config_manager, state_manager, etc.
cmd_class: The command class to instantiate and run.
params: A dict of parameters for the command.

Returns:
True if the command was successfully enqueued and ran, False otherwise.
"""
try:
# Create an instance of the command using app components.
command = cmd_class(app.config_manager.read(), app.state_manager, app.event_manager, params)
print(f"Command {command.get_name()} running...")
# Run the command in a separate thread to avoid blocking the event loop.
result = await asyncio.to_thread(command.run, app.modem_events, app.service_manager.modem)
if result:
return True
except Exception as e:
print(f"Command failed: {e}")
return False
25 changes: 25 additions & 0 deletions freedata_server/api/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from fastapi import HTTPException
from fastapi.responses import JSONResponse

# Returns a standard API response
def api_response(data, status=200):
return JSONResponse(content=data, status_code=status)


def api_abort(message, code):
raise HTTPException(status_code=code, detail={"error": message})


def api_ok(message="ok"):
return api_response({'message': message})


# Validates a parameter
def validate(req, param, validator, is_required=True):
if param not in req:
if is_required:
api_abort(f"Required parameter '{param}' is missing.", 400)
else:
return True
if not validator(req[param]):
api_abort(f"Value of '{param}' is invalid.", 400)
167 changes: 167 additions & 0 deletions freedata_server/api/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
from fastapi import APIRouter, Request
from api.common import api_response, api_abort, api_ok, validate
import api_validations as validations
router = APIRouter()

@router.get("/", summary="Get Modem Configuration", tags=["Configuration"], responses={
200: {
"description": "Current modem configuration settings.",
"content": {
"application/json": {
"example": {
"AUDIO": {
"input_device": "2fc0",
"output_device": "3655",
"rx_audio_level": 0,
"tx_audio_level": 2
},
"MESSAGES": {
"enable_auto_repeat": True
},
"MODEM": {
"enable_hmac": False,
"enable_morse_identifier": False,
"enable_socket_interface": False,
"maximum_bandwidth": 2438,
"tx_delay": 200
},
"NETWORK": {
"modemaddress": "",
"modemport": 5000
},
"RADIO": {
"control": "rigctld",
"data_bits": 8,
"model_id": 1001,
"ptt_port": "ignore",
"ptt_type": "USB",
"serial_dcd": "NONE",
"serial_dtr": "OFF",
"serial_handshake": "ignore",
"serial_port": "/dev/cu.Bluetooth-Incoming-Port",
"serial_speed": 38400,
"stop_bits": 1
},
"RIGCTLD": {
"arguments": "",
"command": "",
"ip": "127.0.0.1",
"path": "",
"port": 4532
},
"SOCKET_INTERFACE": {
"cmd_port": 0,
"data_port": 0,
"enable": False,
"host": ""
},
"STATION": {
"enable_explorer": True,
"enable_stats": True,
"mycall": "LA3QMA",
"mygrid": "JP20ql",
"myssid": 0,
"ssid_list": [0,1,2,3,4,5,6,7,8,9],
"respond_to_cq": True,
},
"TCI": {
"tci_ip": "127.0.0.1",
"tci_port": 50001
}
}
}
}
},
404: {
"description": "The requested resource was not found.",
"content": {
"application/json": {
"example": {
"error": "Resource not found."
}
}
}
}
})
async def get_config(request: Request):
"""
Retrieve the current modem configuration.

Returns:
dict: The modem configuration settings.
"""
return request.app.config_manager.read()


@router.post("/", summary="Update Modem Configuration", tags=["Configuration"], responses={
200: {
"description": "Modem configuration updated successfully.",
"content": {
"application/json": {
"example": {
"AUDIO": {
"input_device": "2fc0",
"output_device": "3655",
"rx_audio_level": 0,
"tx_audio_level": 2
},
# ...
}
}
}
},
400: {
"description": "Invalid configuration data.",
"content": {
"application/json": {
"example": {
"error": "Invalid config"
}
}
}
},
500: {
"description": "Error writing configuration.",
"content": {
"application/json": {
"example": {
"error": "Error writing config"
}
}
}
},
404: {
"description": "The requested resource was not found.",
"content": {
"application/json": {
"example": {
"error": "Resource not found."
}
}
}
}
})
async def post_config(request: Request):
"""
Update the modem configuration with new settings.

Parameters:
request (Request): The HTTP request containing the new configuration in JSON format.

Returns:
dict: The updated modem configuration.

Raises:
HTTPException: If the provided configuration is invalid or an error occurs while writing the config.
"""
config = await request.json()
print(config)
if not validations.validate_remote_config(config):
api_abort("Invalid config", 400)
if request.app.config_manager.read() == config:
return config
set_config = request.app.config_manager.write(config)
if not set_config:
api_abort("Error writing config", 500)
request.app.modem_service.put("restart")
return set_config
147 changes: 147 additions & 0 deletions freedata_server/api/devices.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
from fastapi import APIRouter, Request
from api.common import api_response, api_abort, api_ok, validate
router = APIRouter()
import audio
import serial_ports


@router.get("/audio", summary="Get Audio Devices", tags=["Devices"], responses={
200: {
"description": "List of available audio input and output devices.",
"content": {
"application/json": {
"example": {
"in": [
{
"api": "ALSA",
"id": "8eb1",
"name": "pipewire",
"native_index": 4
},
{
"api": "ALSA",
"id": "8e7a",
"name": "default",
"native_index": 5
}
],
"out": [
{
"api": "ALSA",
"id": "ae79",
"name": "HDA Intel HDMI: 0 (hw:0,3)",
"native_index": 0
},
{
"api": "ALSA",
"id": "67fd",
"name": "HDA Intel HDMI: 1 (hw:0,7)",
"native_index": 1
},
{
"api": "ALSA",
"id": "b68c",
"name": "HDA Intel HDMI: 2 (hw:0,8)",
"native_index": 2
},
{
"api": "ALSA",
"id": "ba84",
"name": "hdmi",
"native_index": 3
},
{
"api": "ALSA",
"id": "8eb1",
"name": "pipewire",
"native_index": 4
},
{
"api": "ALSA",
"id": "8e7a",
"name": "default",
"native_index": 5
}
]
}
}
}
},
404: {
"description": "The requested resource was not found.",
"content": {
"application/json": {
"example": {
"error": "Resource not found."
}
}
}
},
503: {
"description": "Modem not running.",
"content": {
"application/json": {
"example": {
"error": "Modem not running."
}
}
}
}
})
async def get_audio_devices():
"""
Retrieve a list of available audio input and output devices.

Returns:
dict: A JSON object containing lists of input and output audio devices.
"""
# Uncomment the following line if using the actual function
# dev_in, dev_out = audio.get_audio_devices()
dev_in, dev_out = audio.fetch_audio_devices([], [])
return {'in': dev_in, 'out': dev_out}


@router.get("/serial", summary="Get Serial Devices", tags=["Devices"], responses={
200: {
"description": "List of available serial devices (COM ports).",
"content": {
"application/json": {
"example": [
{
"description": "n/a [26a9]",
"port": "/dev/ttyS4"
}
]
}
}
},
404: {
"description": "The requested resource was not found.",
"content": {
"application/json": {
"example": {
"error": "Resource not found."
}
}
}
},
503: {
"description": "Modem not running.",
"content": {
"application/json": {
"example": {
"error": "Modem not running."
}
}
}
}
})
async def get_serial_devices():
"""
Retrieve a list of available serial devices (COM ports).

Returns:
list: A list of dictionaries containing serial port information.
"""
devices = serial_ports.get_ports()
return devices
Loading
Loading