FreeDATA/freedata_server/frame_dispatcher.py

157 lines
7.1 KiB
Python

"""
FRAME DISPATCHER - We are dispatching the received frames to the needed functions
"""
import threading
import structlog
from modem_frametypes import FRAME_TYPE as FR_TYPE
import event_manager
from data_frame_factory import DataFrameFactory
from frame_handler import FrameHandler
from frame_handler_ping import PingFrameHandler
from frame_handler_cq import CQFrameHandler
from frame_handler_arq_session import ARQFrameHandler
from frame_handler_p2p_connection import P2PConnectionFrameHandler
from frame_handler_beacon import BeaconFrameHandler
class DISPATCHER:
"""Dispatches received frames to appropriate handlers.
This class manages the dispatching of received frames to the correct
handler based on the frame type. It initializes frame handlers, starts
worker threads for receiving and processing frames, and provides a
mechanism for stopping the dispatcher.
"""
FRAME_HANDLER = {
FR_TYPE.ARQ_SESSION_OPEN_ACK.value: {"class": ARQFrameHandler, "name": "ARQ OPEN ACK"},
FR_TYPE.ARQ_SESSION_OPEN.value: {"class": ARQFrameHandler, "name": "ARQ Data Channel Open"},
FR_TYPE.ARQ_SESSION_INFO_ACK.value: {"class": ARQFrameHandler, "name": "ARQ INFO ACK"},
FR_TYPE.ARQ_SESSION_INFO.value: {"class": ARQFrameHandler, "name": "ARQ Data Channel Info"},
FR_TYPE.P2P_CONNECTION_CONNECT.value: {"class": P2PConnectionFrameHandler, "name": "P2P Connection CONNECT"},
FR_TYPE.P2P_CONNECTION_CONNECT_ACK.value: {"class": P2PConnectionFrameHandler, "name": "P2P Connection CONNECT ACK"},
FR_TYPE.P2P_CONNECTION_DISCONNECT.value: {"class": P2PConnectionFrameHandler, "name": "P2P Connection DISCONNECT"},
FR_TYPE.P2P_CONNECTION_DISCONNECT_ACK.value: {"class": P2PConnectionFrameHandler,
"name": "P2P Connection DISCONNECT ACK"},
FR_TYPE.P2P_CONNECTION_PAYLOAD.value: {"class": P2PConnectionFrameHandler,
"name": "P2P Connection PAYLOAD"},
FR_TYPE.P2P_CONNECTION_PAYLOAD_ACK.value: {"class": P2PConnectionFrameHandler,
"name": "P2P Connection PAYLOAD ACK"},
FR_TYPE.P2P_CONNECTION_HEARTBEAT.value: {"class": P2PConnectionFrameHandler,
"name": "P2P Connection HEARTBEAT"},
FR_TYPE.P2P_CONNECTION_HEARTBEAT_ACK.value: {"class": P2PConnectionFrameHandler,
"name": "P2P Connection HEARTBEAT ACK"},
FR_TYPE.ARQ_STOP.value: {"class": ARQFrameHandler, "name": "ARQ STOP"},
FR_TYPE.ARQ_STOP_ACK.value: {"class": ARQFrameHandler, "name": "ARQ STOP ACK"},
FR_TYPE.BEACON.value: {"class": BeaconFrameHandler, "name": "BEACON"},
FR_TYPE.ARQ_BURST_FRAME.value:{"class": ARQFrameHandler, "name": "BURST FRAME"},
FR_TYPE.ARQ_BURST_ACK.value: {"class": ARQFrameHandler, "name": "BURST ACK"},
FR_TYPE.CQ.value: {"class": CQFrameHandler, "name": "CQ"},
FR_TYPE.PING_ACK.value: {"class": FrameHandler, "name": "PING ACK"},
FR_TYPE.PING.value: {"class": PingFrameHandler, "name": "PING"},
FR_TYPE.QRV.value: {"class": FrameHandler, "name": "QRV"},
#FR_TYPE.IS_WRITING.value: {"class": FrameHandler, "name": "IS_WRITING"},
#FR_TYPE.FEC.value: {"class": FrameHandler, "name": "FEC"},
#FR_TYPE.FEC_WAKEUP.value: {"class": FrameHandler, "name": "FEC WAKEUP"},
}
def __init__(self, ctx):
"""Initializes the frame dispatcher.
This method sets up the frame dispatcher with the provided
configuration, event manager, state manager, and modem. It
initializes frame handlers and starts the receive worker thread.
Args:
config (dict): The configuration dictionary.
event_manager (EventManager): The event manager object.
states (StateManager): The state manager object.
modem: The modem object.
"""
self.log = structlog.get_logger("frame_dispatcher")
self.ctx = ctx
self.log.info("loading frame dispatcher.....\n")
self.stop_event = threading.Event()
self.frame_factory = DataFrameFactory(self.ctx)
self.arq_sessions = []
def start(self):
"""Starts worker threads for transmit and receive operations."""
threading.Thread(target=self.worker_receive, name="Receive Worker", daemon=True).start()
def stop(self):
self.stop_event.set()
def worker_receive(self) -> None:
"""Queue received data for processing"""
while not self.stop_event.is_set():
try:
data = self.ctx.rf_modem.data_queue_received.get(timeout=1)
if data:
self.process_data(
data['payload'],
data['freedv'],
data['bytes_per_frame'],
data['snr'],
data['frequency_offset'],
data['mode_name'],
)
except Exception:
continue
def process_data(self, bytes_out, freedv, bytes_per_frame: int, snr, frequency_offset, mode_name) -> None:
"""Processes received data frames.
This method deconstructs the received data into a frame dictionary,
identifies the frame type, and dispatches the frame to the
appropriate handler based on its type. It logs warnings for
unknown frame types.
Args:
bytes_out (bytes): The raw frame data.
freedv: The FreeDV instance.
bytes_per_frame (int): The number of bytes per frame.
snr (float): The signal-to-noise ratio of the received frame.
frequency_offset (float): The frequency offset of the received frame.
mode_name (str): The name of the FreeDV mode.
"""
# get frame as dictionary
deconstructed_frame = self.frame_factory.deconstruct(bytes_out, mode_name=mode_name)
frametype = deconstructed_frame["frame_type_int"]
if frametype not in self.FRAME_HANDLER:
self.log.warning(
"[DISPATCHER] unknown burst", frametype=FR_TYPE(frametype).name)
return
# instantiate handler
handler_class = self.FRAME_HANDLER[frametype]['class']
handler: FrameHandler = handler_class(self.ctx, self.FRAME_HANDLER[frametype]['name'])
handler.handle(deconstructed_frame, snr, frequency_offset, freedv, bytes_per_frame)
def get_id_from_frame(self, data):
"""Extracts the session ID from an ARQ_SESSION_OPEN frame.
This method checks if the provided data represents an
ARQ_SESSION_OPEN frame and, if so, extracts and returns the session
ID. Otherwise, it returns None. This method is currently not used
in the code.
Args:
data (bytes): The frame data.
Returns:
bytes or None: The session ID if the frame is an ARQ_SESSION_OPEN
frame, None otherwise.
"""
if data[:1] == FR_TYPE.ARQ_SESSION_OPEN:
return data[13:14]
return None