Init push
This commit is contained in:
171
client.py
Normal file
171
client.py
Normal file
@@ -0,0 +1,171 @@
|
||||
import asyncio
|
||||
import websockets
|
||||
import json
|
||||
import uuid # To generate a unique client ID
|
||||
from drb_cdb_api import DRBCDBAPI, ConfigGenerator
|
||||
from server_api import RadioAPIClient
|
||||
from enum import Enum
|
||||
|
||||
# --- Client Configuration ---
|
||||
SERVER_WS_URI = "ws://localhost:8765"
|
||||
SERVER_API_URL = "http://localhost:5000"
|
||||
CLIENT_API_URL = "http://localhost:8001"
|
||||
# Generate or define a unique ID for THIS client instance
|
||||
# In a real app, this might come from config or a login process
|
||||
CLIENT_ID = f"client-{uuid.uuid4().hex[:8]}" # TODO - Implement persistent ID
|
||||
# ----------------------------
|
||||
|
||||
# Dictionary mapping command names (strings) to local client functions
|
||||
command_handlers = {}
|
||||
|
||||
# Init DRB API handler
|
||||
drb_api = DRBCDBAPI(CLIENT_API_URL)
|
||||
srv_api = RadioAPIClient(SERVER_API_URL)
|
||||
|
||||
# --- Define the client status object ---
|
||||
class StatusValues(Enum):
|
||||
ONLINE = "online" # The client is online
|
||||
LISTENING = "listening" # The client bot is online and listening to a system
|
||||
|
||||
client_status = StatusValues.ONLINE
|
||||
|
||||
# --- Define decorator creation function ---
|
||||
def command(func):
|
||||
"""Decorator to register a function as a command handler."""
|
||||
command_handlers[func.__name__] = func
|
||||
return func
|
||||
|
||||
# --- Define Client-Side Command Handlers (The "API" functions) ---
|
||||
|
||||
# Join server
|
||||
@command
|
||||
async def join_server(system_id, guild_id, channel_id):
|
||||
# Takes system ID, guild ID, channel ID
|
||||
bot_status = drb_api.get_bot_status()
|
||||
# Check if the bot is running
|
||||
if 'bot_running' not in bot_status or not bot_status['bot_running']:
|
||||
# Run the bot if not
|
||||
drb_api.start_bot()
|
||||
# Update status
|
||||
client_status = StatusValues.LISTENING
|
||||
op25_status = drb_api.get_op25_status()
|
||||
|
||||
# Check if OP25 is stopped, if so set the selected channel, otherwise
|
||||
if op25_status == "stopped":
|
||||
chn_details = srv_api.get_channel_details(channel_id)
|
||||
if not chn_details:
|
||||
# TODO - handle not having channel details
|
||||
pass
|
||||
|
||||
# Generate the config for the channel requested
|
||||
chn_config = ConfigGenerator(
|
||||
type=chn_details['decode_mode'],
|
||||
systemName=chn_details['name'],
|
||||
channels=chn_details['frequency_list_khz'],
|
||||
tags=chn_details['tags'],
|
||||
whitelist=chn_details['tag_whitelist'])
|
||||
|
||||
# Set the OP25 config
|
||||
drb_api.generate_op25_config(chn_config)
|
||||
|
||||
# Start OP25
|
||||
drb_api.start_op25()
|
||||
|
||||
# Leave server
|
||||
@command
|
||||
async def leave_server(guild_id):
|
||||
# Takes guild ID
|
||||
bot_status = drb_api.get_bot_status()
|
||||
|
||||
# Check if the bot is running
|
||||
if 'bot_running' not in bot_status or not bot_status['bot_running']:
|
||||
# If not, do nothing
|
||||
return
|
||||
|
||||
# Check if the bot is in the guild
|
||||
if not "connected_guilds" in bot_status or guild_id not in bot_status['connected_guilds']:
|
||||
return
|
||||
|
||||
# Leave the server specified
|
||||
drb_api.leave_voice_channel(guild_id)
|
||||
|
||||
# Update status
|
||||
client_status = StatusValues.ONLINE
|
||||
|
||||
@command
|
||||
async def set_status(status_text):
|
||||
"""Example command: Sets or displays a status."""
|
||||
print(f"\n--- Server Command: set_status ---")
|
||||
print(f"Status updated to: {status_text}")
|
||||
print("----------------------------------")
|
||||
|
||||
@command
|
||||
async def run_task(task_id, duration_seconds):
|
||||
"""Example command: Simulates running a task."""
|
||||
print(f"\n--- Server Command: run_task ---")
|
||||
print(f"Starting task {task_id} for {duration_seconds} seconds...")
|
||||
await asyncio.sleep(duration_seconds)
|
||||
print(f"Task {task_id} finished.")
|
||||
print("------------------------------")
|
||||
|
||||
# Add more command handlers as needed...
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
|
||||
async def receive_commands(websocket):
|
||||
"""Listens for and processes command messages from the server."""
|
||||
async for message in websocket:
|
||||
try:
|
||||
data = json.loads(message)
|
||||
if data.get("type") == "command":
|
||||
command_name = data.get("name")
|
||||
args = data.get("args", [])
|
||||
|
||||
if command_name in command_handlers:
|
||||
print(f"Executing command: {command_name} with args {args}")
|
||||
# Execute the registered async function
|
||||
await command_handlers[command_name](*args)
|
||||
else:
|
||||
print(f"Received unknown command: {command_name}")
|
||||
elif data.get("type") == "handshake_ack":
|
||||
print(f"Server acknowledged handshake.")
|
||||
else:
|
||||
print(f"Received unknown message type: {data.get('type')}")
|
||||
|
||||
except json.JSONDecodeError:
|
||||
print(f"Received invalid JSON: {message}")
|
||||
except Exception as e:
|
||||
print(f"Error processing message: {e}")
|
||||
|
||||
async def main_client():
|
||||
"""Connects to the server and handles communication."""
|
||||
print(f"Client {CLIENT_ID} connecting to {SERVER_WS_URI}...")
|
||||
try:
|
||||
async with websockets.connect(SERVER_WS_URI) as websocket:
|
||||
print("Connection established.")
|
||||
|
||||
# Handshake: Send client ID immediately after connecting
|
||||
handshake_message = json.dumps({"type": "handshake", "id": CLIENT_ID})
|
||||
await websocket.send(handshake_message)
|
||||
print(f"Sent handshake with ID: {CLIENT_ID}")
|
||||
|
||||
# Start receiving commands and keep the connection alive
|
||||
await receive_commands(websocket)
|
||||
|
||||
except ConnectionRefusedError:
|
||||
print(f"Connection refused. Is the server running at {SERVER_WS_URI}?")
|
||||
except websockets.exceptions.ConnectionClosedOK:
|
||||
print("Connection closed gracefully by server.")
|
||||
except websockets.exceptions.ConnectionClosedError as e:
|
||||
print(f"Connection closed unexpectedly: {e}")
|
||||
except Exception as e:
|
||||
print(f"An error occurred: {e}")
|
||||
|
||||
print(f"Client {CLIENT_ID} stopped.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Note: In a real application, you'd want a more robust way
|
||||
# to manage the event loop and handle potential reconnections.
|
||||
asyncio.run(main_client())
|
||||
Reference in New Issue
Block a user