mirror of https://github.com/DJ2LS/FreeDATA.git
239 lines
9.2 KiB
Python
239 lines
9.2 KiB
Python
import threading
|
|
import frame_dispatcher
|
|
import modem
|
|
import structlog
|
|
import audio
|
|
import radio_manager
|
|
from socket_interface import SocketInterfaceHandler
|
|
import queue
|
|
|
|
class SM:
|
|
"""Manages the FreeDATA server services.
|
|
|
|
This class controls the starting, stopping, and restarting of the modem,
|
|
radio manager, and socket interface. It handles commands from the modem
|
|
service queue and performs actions based on the received commands.
|
|
"""
|
|
def __init__(self, ctx):
|
|
"""Initializes the service manager.
|
|
|
|
This method sets up the service manager with references to the main
|
|
application object and its components, including the config manager,
|
|
modem, radio manager, state manager, event manager, and schedule
|
|
manager. It also initializes the socket interface manager if enabled
|
|
in the configuration and starts the runner thread.
|
|
|
|
Args:
|
|
app: The main application object.
|
|
"""
|
|
|
|
|
|
self.log = structlog.get_logger("service manager")
|
|
self.ctx = ctx
|
|
self.ctx.rf_modem = False
|
|
|
|
self.shutdown_flag = threading.Event()
|
|
|
|
|
|
self.runner_thread = threading.Thread(
|
|
target=self.runner, name="runner thread", daemon=False
|
|
)
|
|
|
|
self.runner_thread.start()
|
|
|
|
|
|
def runner(self):
|
|
"""Main loop for handling service commands.
|
|
|
|
This method continuously monitors the modem service queue for
|
|
commands and executes the corresponding actions. It handles starting,
|
|
stopping, and restarting the modem, radio manager, and socket
|
|
interface.
|
|
"""
|
|
while not self.shutdown_flag.is_set():
|
|
try:
|
|
cmd = self.ctx.modem_service.get()
|
|
if self.shutdown_flag.is_set():
|
|
return
|
|
|
|
if cmd in ['start'] and not self.ctx.rf_modem:
|
|
self.ctx.config_manager.read()
|
|
|
|
self.start_radio_manager()
|
|
self.start_modem()
|
|
|
|
if self.ctx.config_manager.config['SOCKET_INTERFACE']['enable']:
|
|
self.ctx.socket_interface_manager = SocketInterfaceHandler(self.ctx).start_servers()
|
|
else:
|
|
self.ctx.socket_interface_manager = None
|
|
|
|
elif cmd in ['stop'] and self.ctx.rf_modem:
|
|
self.stop_modem()
|
|
self.stop_radio_manager()
|
|
if self.ctx.config_manager.config['SOCKET_INTERFACE']['enable'] and self.ctx.socket_interface_manager:
|
|
self.ctx.socket_interface_manager.stop_servers()
|
|
# we need to wait a bit for avoiding a portaudio crash
|
|
threading.Event().wait(0.5)
|
|
|
|
elif cmd in ['restart']:
|
|
self.stop_modem()
|
|
self.stop_radio_manager()
|
|
if self.ctx.config_manager.config['SOCKET_INTERFACE']['enable'] and self.ctx.socket_interface_manager:
|
|
self.ctx.socket_interface_manager.stop_servers()
|
|
del self.ctx.socket_interface_manager
|
|
|
|
# we need to wait a bit for avoiding a portaudio crash
|
|
threading.Event().wait(0.5)
|
|
self.log.info("reading config...")
|
|
self.ctx.config_manager.read()
|
|
|
|
self.log.info("starting modem")
|
|
if self.start_modem():
|
|
self.ctx.event_manager.modem_restarted()
|
|
|
|
self.log.info("starting radio manager")
|
|
self.start_radio_manager()
|
|
|
|
if self.ctx.config_manager.config['SOCKET_INTERFACE']['enable']:
|
|
self.ctx.socket_interface_manager = SocketInterfaceHandler(self.ctx).start_servers()
|
|
else:
|
|
self.ctx.socket_interface_manager = None
|
|
|
|
else:
|
|
if not self.shutdown_flag.is_set():
|
|
self.log.warning("[SVC] freedata_server command processing failed", cmd=cmd, state=self.ctx.state_manager.is_modem_running)
|
|
|
|
# Queue is empty
|
|
except queue.Empty:
|
|
pass
|
|
|
|
# finally clear processing commands
|
|
self.ctx.modem_service.queue.clear()
|
|
|
|
def start_modem(self):
|
|
"""Starts the FreeDATA modem.
|
|
|
|
This method initializes and starts the RF modem, frame dispatcher,
|
|
and schedule manager. It performs checks for valid callsign and
|
|
audio device functionality before starting the modem.
|
|
|
|
Returns:
|
|
bool: True if the modem started successfully, False otherwise.
|
|
"""
|
|
|
|
if self.ctx.config_manager.config['STATION']['mycall'] in ['XX1XXX']:
|
|
self.log.warning("wrong callsign in config! interrupting startup")
|
|
return False
|
|
|
|
if self.ctx.state_manager.is_modem_running:
|
|
self.log.warning("freedata_server already running")
|
|
return False
|
|
|
|
if not self.ctx.TESTMODE:
|
|
# test audio devices
|
|
audio_test = self.test_audio()
|
|
if False in audio_test or None in audio_test or self.ctx.state_manager.is_modem_running:
|
|
self.log.warning("starting freedata_server failed", input_test=audio_test[0], output_test=audio_test[1])
|
|
self.ctx.state_manager.set("is_modem_running", False)
|
|
self.ctx.event_manager.modem_failed()
|
|
return False
|
|
|
|
self.log.info("starting freedata_server....")
|
|
self.ctx.rf_modem = modem.RF(self.ctx)
|
|
self.frame_dispatcher = frame_dispatcher.DISPATCHER(self.ctx)
|
|
self.frame_dispatcher.start()
|
|
|
|
self.ctx.event_manager.modem_started()
|
|
self.ctx.state_manager.set("is_modem_running", True)
|
|
self.ctx.rf_modem.start_modem()
|
|
self.ctx.schedule_manager.start(self.ctx.rf_modem)
|
|
|
|
return True
|
|
|
|
def stop_modem(self):
|
|
"""Stops the FreeDATA modem and related services.
|
|
|
|
This method stops the RF modem, frame dispatcher, and schedule
|
|
manager. It also updates the modem running state and emits a
|
|
'modem_stopped' event. It handles potential AttributeErrors that
|
|
may occur during the stopping process if components are not
|
|
initialized.
|
|
"""
|
|
self.log.warning("stopping modem....")
|
|
try:
|
|
if self.ctx.rf_modem and hasattr(self.ctx, 'modem_service'):
|
|
self.ctx.rf_modem.stop_modem()
|
|
del self.ctx.rf_modem
|
|
self.ctx.rf_modem = False
|
|
except AttributeError:
|
|
pass
|
|
self.ctx.state_manager.set("is_modem_running", False)
|
|
try:
|
|
if self.ctx.schedule_manager and hasattr(self.ctx, 'schedule_manager'):
|
|
self.ctx.schedule_manager.stop()
|
|
except AttributeError:
|
|
pass
|
|
try:
|
|
if self.frame_dispatcher and hasattr(self.ctx, 'frame_dispatcher'):
|
|
self.frame_dispatcher.stop()
|
|
except AttributeError:
|
|
pass
|
|
|
|
self.ctx.event_manager.modem_stopped()
|
|
|
|
def test_audio(self):
|
|
"""Tests the configured audio devices.
|
|
|
|
This method tests the input and output audio devices specified in
|
|
the configuration. It logs the test results and returns a list
|
|
indicating whether each device passed the test.
|
|
|
|
Returns:
|
|
list: A list of booleans, where the first element represents the
|
|
input device test result and the second element represents the
|
|
output device test result. Returns [False, False] if an error
|
|
occurs during testing.
|
|
"""
|
|
try:
|
|
audio_test = audio.test_audio_devices(self.ctx.config_manager.config['AUDIO']['input_device'],
|
|
self.ctx.config_manager.config['AUDIO']['output_device'])
|
|
self.log.info("tested audio devices", result=audio_test)
|
|
|
|
return audio_test
|
|
except Exception as e:
|
|
self.log.error("Error testing audio devices", e=e)
|
|
return [False, False]
|
|
|
|
def start_radio_manager(self):
|
|
|
|
"""Starts the radio manager.
|
|
|
|
This method initializes and starts the RadioManager, which handles
|
|
communication with the radio.
|
|
"""
|
|
self.ctx.radio_manager = radio_manager.RadioManager(self.ctx)
|
|
|
|
def stop_radio_manager(self):
|
|
"""Stops the radio manager.
|
|
|
|
This method stops the RadioManager and releases the associated
|
|
resources. It handles potential AttributeErrors if the radio manager
|
|
has not been initialized.
|
|
"""
|
|
if hasattr(self.ctx, 'radio_manager'):
|
|
self.ctx.radio_manager.stop()
|
|
del self.ctx.radio_manager
|
|
|
|
def shutdown(self):
|
|
"""Shuts down the service manager.
|
|
|
|
This method stops the modem, sets the shutdown flag, and waits for
|
|
the runner thread to finish. This ensures a clean shutdown of all
|
|
managed services.
|
|
"""
|
|
self.log.warning("[SHUTDOWN] stopping service manager....")
|
|
self.ctx.modem_service.put("stop")
|
|
threading.Event().wait(2) # we need some time before processing with the shutdown_event_flag
|
|
self.shutdown_flag.set()
|
|
self.runner_thread.join(0.5)
|