mirror of https://github.com/DJ2LS/FreeDATA.git
first run of docstring session....
parent
0de64c6a03
commit
e6b3e83790
|
@ -90,13 +90,22 @@ class CONFIG:
|
|||
}
|
||||
|
||||
def __init__(self, configfile: str):
|
||||
"""Initializes a new CONFIG instance.
|
||||
|
||||
This method initializes the configuration handler with the specified
|
||||
config file. It sets up the logger, config parser, and validates
|
||||
the config file's existence and structure.
|
||||
|
||||
Args:
|
||||
configfile (str): The path to the configuration file.
|
||||
"""
|
||||
|
||||
# set up logger
|
||||
self.log = structlog.get_logger(type(self).__name__)
|
||||
|
||||
# init configparser
|
||||
self.parser = configparser.ConfigParser(inline_comment_prefixes="#", allow_no_value=True)
|
||||
|
||||
|
||||
try:
|
||||
self.config_name = configfile
|
||||
except Exception:
|
||||
|
@ -109,10 +118,16 @@ class CONFIG:
|
|||
|
||||
# validate config structure
|
||||
self.validate_config()
|
||||
|
||||
|
||||
def config_exists(self):
|
||||
"""
|
||||
check if config file exists
|
||||
"""Checks if the configuration file exists and can be read.
|
||||
|
||||
This method attempts to read the configuration file and returns True
|
||||
if successful, False otherwise. It logs any errors encountered during
|
||||
the reading process.
|
||||
|
||||
Returns:
|
||||
bool: True if the config file exists and is readable, False otherwise.
|
||||
"""
|
||||
try:
|
||||
return bool(self.parser.read(self.config_name, None))
|
||||
|
@ -122,6 +137,18 @@ class CONFIG:
|
|||
|
||||
# Validates config data
|
||||
def validate_data(self, data):
|
||||
"""Validates the data types of configuration settings.
|
||||
|
||||
This method checks if the provided data matches the expected data
|
||||
types defined in config_types. It raises a ValueError if a data type
|
||||
mismatch is found.
|
||||
|
||||
Args:
|
||||
data (dict): The configuration data to validate.
|
||||
|
||||
Raises:
|
||||
ValueError: If a data type mismatch is found.
|
||||
"""
|
||||
for section in data:
|
||||
for setting in data[section]:
|
||||
if not isinstance(data[section][setting], self.config_types[section][setting]):
|
||||
|
@ -130,9 +157,16 @@ class CONFIG:
|
|||
raise ValueError(message)
|
||||
|
||||
def validate_config(self):
|
||||
"""
|
||||
Updates the configuration file to match exactly what is defined in self.config_types.
|
||||
It removes sections and settings not defined there and adds missing sections and settings.
|
||||
"""Validates and updates the configuration file structure.
|
||||
|
||||
This method checks the existing configuration file against the
|
||||
defined config_types. It removes any undefined sections or settings,
|
||||
adds missing sections and settings with default values, and then
|
||||
writes the updated configuration back to the file.
|
||||
|
||||
Returns:
|
||||
dict or bool: A dictionary containing the updated configuration
|
||||
data if successful, False otherwise.
|
||||
"""
|
||||
existing_sections = self.parser.sections()
|
||||
|
||||
|
@ -162,10 +196,24 @@ class CONFIG:
|
|||
|
||||
return self.write_to_file()
|
||||
|
||||
# Handle special setting data type conversion
|
||||
# is_writing means data from a dict being writen to the config file
|
||||
# if False, it means the opposite direction
|
||||
def handle_setting(self, section, setting, value, is_writing=False):
|
||||
"""Handles special data type conversions for config settings.
|
||||
|
||||
This method performs data type conversions for specific config settings,
|
||||
such as lists and booleans, when reading from or writing to the config
|
||||
file. It also handles KeyErrors if a setting is not found in the
|
||||
config_types dictionary.
|
||||
|
||||
Args:
|
||||
section (str): The config section name.
|
||||
setting (str): The config setting name.
|
||||
value: The value to be converted.
|
||||
is_writing (bool, optional): True if writing to the config file,
|
||||
False if reading from it. Defaults to False.
|
||||
|
||||
Returns:
|
||||
The converted value, or the original value if no conversion is needed.
|
||||
"""
|
||||
try:
|
||||
if self.config_types[section][setting] == list:
|
||||
if is_writing:
|
||||
|
@ -192,6 +240,20 @@ class CONFIG:
|
|||
|
||||
# Sets and writes config data from a dict containing data settings
|
||||
def write(self, data):
|
||||
"""Writes the provided data to the configuration file.
|
||||
|
||||
This method validates the input data, converts it to the appropriate
|
||||
types, writes it to the configuration file, and returns the updated
|
||||
configuration as a dictionary. It logs any errors encountered
|
||||
during the writing process.
|
||||
|
||||
Args:
|
||||
data (dict): A dictionary containing the configuration data to write.
|
||||
|
||||
Returns:
|
||||
dict or bool: A dictionary containing the updated configuration
|
||||
data if successful, False otherwise.
|
||||
"""
|
||||
# Validate config data before writing
|
||||
print(data)
|
||||
self.validate_data(data)
|
||||
|
@ -210,7 +272,17 @@ class CONFIG:
|
|||
return self.write_to_file()
|
||||
|
||||
def write_to_file(self):
|
||||
# Write config data to file
|
||||
"""Writes the current configuration to the config file.
|
||||
|
||||
This method writes the in-memory configuration data to the
|
||||
configuration file. It then rereads and returns the updated
|
||||
configuration. It logs any errors encountered during the writing
|
||||
process.
|
||||
|
||||
Returns:
|
||||
dict or bool: A dictionary containing the updated configuration
|
||||
data if successful, False otherwise.
|
||||
"""
|
||||
try:
|
||||
with open(self.config_name, 'w') as configfile:
|
||||
self.parser.write(configfile)
|
||||
|
@ -220,8 +292,15 @@ class CONFIG:
|
|||
return False
|
||||
|
||||
def read(self):
|
||||
"""
|
||||
read config file
|
||||
"""Reads the configuration file.
|
||||
|
||||
This method reads the configuration file, handles special setting data
|
||||
type conversions, and returns the configuration as a dictionary.
|
||||
It logs any errors encountered during the reading process.
|
||||
|
||||
Returns:
|
||||
dict or bool: A dictionary containing the configuration data if
|
||||
successful, False otherwise.
|
||||
"""
|
||||
# self.log.info("[CFG] reading...")
|
||||
if not self.config_exists():
|
||||
|
|
|
@ -9,7 +9,22 @@ import numpy as np
|
|||
|
||||
|
||||
class MorseCodePlayer:
|
||||
"""Generates and plays morse code audio.
|
||||
|
||||
This class provides functionality to convert text to morse code and then
|
||||
to an audio signal, allowing for the generation and playback of morse
|
||||
code. It supports customization of the code speed (WPM), tone frequency,
|
||||
and sampling rate.
|
||||
"""
|
||||
|
||||
def __init__(self, wpm=25, f=1500, fs=48000):
|
||||
"""Initializes the MorseCodePlayer.
|
||||
|
||||
Args:
|
||||
wpm (int, optional): Words per minute, defining the speed of the morse code. Defaults to 25.
|
||||
f (int, optional): Tone frequency in Hz. Defaults to 1500.
|
||||
fs (int, optional): Sampling rate in Hz. Defaults to 48000.
|
||||
"""
|
||||
self.wpm = wpm
|
||||
self.f0 = f
|
||||
self.fs = fs
|
||||
|
@ -29,6 +44,18 @@ class MorseCodePlayer:
|
|||
}
|
||||
|
||||
def text_to_morse(self, text):
|
||||
"""Converts text to morse code.
|
||||
|
||||
This method takes a string of text as input and converts it to morse
|
||||
code, using the defined morse alphabet. It handles spaces and
|
||||
non-alphanumeric characters.
|
||||
|
||||
Args:
|
||||
text (str): The text to convert.
|
||||
|
||||
Returns:
|
||||
str: The morse code representation of the input text.
|
||||
"""
|
||||
morse = ''
|
||||
for char in text:
|
||||
if char.upper() in self.morse_alphabet:
|
||||
|
@ -38,6 +65,19 @@ class MorseCodePlayer:
|
|||
return morse
|
||||
|
||||
def morse_to_signal(self, morse):
|
||||
"""Converts morse code to an audio signal.
|
||||
|
||||
This method takes a string of morse code as input and generates a
|
||||
corresponding audio signal. Dots and dashes are represented by sine
|
||||
waves of appropriate durations, and pauses are represented by
|
||||
silence.
|
||||
|
||||
Args:
|
||||
morse (str): The morse code string to convert.
|
||||
|
||||
Returns:
|
||||
numpy.ndarray: The generated audio signal.
|
||||
"""
|
||||
signal = np.array([], dtype=np.int16)
|
||||
for char in morse:
|
||||
if char == '.':
|
||||
|
@ -65,6 +105,18 @@ class MorseCodePlayer:
|
|||
return signal
|
||||
|
||||
def text_to_signal(self, text):
|
||||
"""Converts text to a morse code audio signal.
|
||||
|
||||
This method takes text as input, converts it to morse code using the
|
||||
`text_to_morse` method, and then converts the morse code to an audio
|
||||
signal using the `morse_to_signal` method.
|
||||
|
||||
Args:
|
||||
text (str): The text to convert to an audio signal.
|
||||
|
||||
Returns:
|
||||
numpy.ndarray: The generated audio signal as a NumPy array.
|
||||
"""
|
||||
morse = self.text_to_morse(text)
|
||||
return self.morse_to_signal(morse)
|
||||
|
||||
|
|
|
@ -2,6 +2,14 @@ import time
|
|||
import threading
|
||||
import numpy as np
|
||||
class StateManager:
|
||||
"""Manages and updates the state of the FreeDATA server.
|
||||
|
||||
This class stores and manages various state variables related to the
|
||||
FreeDATA server, including modem status, channel activity, ARQ sessions,
|
||||
radio parameters, and P2P connections. It provides methods to update
|
||||
and retrieve state information, as well as manage events and
|
||||
synchronization.
|
||||
"""
|
||||
def __init__(self, statequeue):
|
||||
|
||||
# state related settings
|
||||
|
@ -55,14 +63,40 @@ class StateManager:
|
|||
self.radio_status = False
|
||||
|
||||
def sendState(self):
|
||||
"""Sends the current state to the state queue.
|
||||
|
||||
This method retrieves the current state using get_state_event() and
|
||||
puts it into the state queue for processing and distribution.
|
||||
|
||||
Returns:
|
||||
dict: The current state.
|
||||
"""
|
||||
currentState = self.get_state_event(False)
|
||||
self.statequeue.put(currentState)
|
||||
return currentState
|
||||
|
||||
def sendStateUpdate(self, state):
|
||||
"""Sends a state update to the state queue.
|
||||
|
||||
This method puts the given state update into the state queue for
|
||||
processing and distribution.
|
||||
|
||||
Args:
|
||||
state (dict): The state update to send.
|
||||
"""
|
||||
self.statequeue.put(state)
|
||||
|
||||
def set(self, key, value):
|
||||
"""Sets a state variable and sends an update if the state changes.
|
||||
|
||||
This method sets the specified state variable to the given value.
|
||||
If the new state is different from the current state, it generates
|
||||
a state update event and sends it to the state queue.
|
||||
|
||||
Args:
|
||||
key (str): The name of the state variable.
|
||||
value: The new value for the state variable.
|
||||
"""
|
||||
setattr(self, key, value)
|
||||
#print(f"State ==> Setting {key} to value {value}")
|
||||
# only process data if changed
|
||||
|
@ -72,6 +106,16 @@ class StateManager:
|
|||
self.sendStateUpdate(new_state)
|
||||
|
||||
def set_radio(self, key, value):
|
||||
"""Sets a radio parameter and sends an update if the value changes.
|
||||
|
||||
This method sets the specified radio parameter to the given value.
|
||||
If the new value is different from the current value, it generates
|
||||
a radio update event and sends it to the state queue.
|
||||
|
||||
Args:
|
||||
key (str): The name of the radio parameter.
|
||||
value: The new value for the radio parameter.
|
||||
"""
|
||||
setattr(self, key, value)
|
||||
#print(f"State ==> Setting {key} to value {value}")
|
||||
# only process data if changed
|
||||
|
@ -81,6 +125,15 @@ class StateManager:
|
|||
self.sendStateUpdate(new_radio)
|
||||
|
||||
def set_channel_slot_busy(self, array):
|
||||
"""Sets the channel busy status for each slot.
|
||||
|
||||
This method updates the channel busy status for each slot based on
|
||||
the provided array. If any slot's status changes, it generates a
|
||||
state update event and sends it to the state queue.
|
||||
|
||||
Args:
|
||||
array (list): A list of booleans representing the busy status of each slot.
|
||||
"""
|
||||
for i in range(0,len(array),1):
|
||||
if not array[i] == self.channel_busy_slot[i]:
|
||||
self.channel_busy_slot = array
|
||||
|
@ -89,6 +142,20 @@ class StateManager:
|
|||
continue
|
||||
|
||||
def get_state_event(self, isChangedState):
|
||||
"""Generates a state event dictionary.
|
||||
|
||||
This method creates a dictionary containing the current state
|
||||
information, including modem and beacon status, channel activity,
|
||||
radio status, and audio levels. The type of event ('state' or
|
||||
'state-change') is determined by the isChangedState flag.
|
||||
|
||||
Args:
|
||||
isChangedState (bool): True if the event represents a state change,
|
||||
False if it's a full state update.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the state information.
|
||||
"""
|
||||
msgtype = "state-change"
|
||||
if (not isChangedState):
|
||||
msgtype = "state"
|
||||
|
@ -107,6 +174,20 @@ class StateManager:
|
|||
}
|
||||
|
||||
def get_radio_event(self, isChangedState):
|
||||
"""Generates a radio event dictionary.
|
||||
|
||||
This method creates a dictionary containing the current radio state
|
||||
information, including frequency, mode, RF level, S-meter strength,
|
||||
SWR, and tuner status. The type of event ('radio' or 'radio-change')
|
||||
is determined by the isChangedState flag.
|
||||
|
||||
Args:
|
||||
isChangedState (bool): True if this is a radio change event,
|
||||
False for a full radio state update.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the radio state information.
|
||||
"""
|
||||
msgtype = "radio-change"
|
||||
if (not isChangedState):
|
||||
msgtype = "radio"
|
||||
|
|
|
@ -5,7 +5,19 @@ import structlog
|
|||
|
||||
|
||||
class wsm:
|
||||
"""Manages WebSocket connections and data transmission.
|
||||
|
||||
This class handles WebSocket connections from clients, manages client
|
||||
lists for different data types (events, FFT, states), and transmits
|
||||
data to connected clients via worker threads. It ensures a clean
|
||||
shutdown of WebSocket connections and related resources.
|
||||
"""
|
||||
def __init__(self):
|
||||
"""Initializes the WebSocket manager.
|
||||
|
||||
This method sets up the logger, shutdown flag, client lists for
|
||||
different data types, and worker threads.
|
||||
"""
|
||||
self.log = structlog.get_logger("WEBSOCKET_MANAGER")
|
||||
self.shutdown_flag = threading.Event()
|
||||
|
||||
|
@ -19,6 +31,17 @@ class wsm:
|
|||
self.fft_thread = None
|
||||
|
||||
async def handle_connection(self, websocket, client_list, event_queue):
|
||||
"""Handles a WebSocket connection.
|
||||
|
||||
This method adds the new client to the provided list and continuously
|
||||
listens for incoming messages. If a client disconnects, it removes
|
||||
the client from the list and logs the event.
|
||||
|
||||
Args:
|
||||
websocket: The WebSocket object representing the client connection.
|
||||
client_list (set): The set of connected WebSocket clients.
|
||||
event_queue (queue.Queue): The event queue. Currently unused.
|
||||
"""
|
||||
client_list.add(websocket)
|
||||
while not self.shutdown_flag.is_set():
|
||||
try:
|
||||
|
@ -32,6 +55,16 @@ class wsm:
|
|||
break
|
||||
|
||||
def transmit_sock_data_worker(self, client_list, event_queue):
|
||||
"""Worker thread function for transmitting data to WebSocket clients.
|
||||
|
||||
This method continuously retrieves events from the provided queue and
|
||||
sends them as JSON strings to all connected clients in the specified
|
||||
list. It handles client disconnections gracefully.
|
||||
|
||||
Args:
|
||||
client_list (set): The set of connected WebSocket clients.
|
||||
event_queue (queue.Queue): The queue containing events to be transmitted.
|
||||
"""
|
||||
while not self.shutdown_flag.is_set():
|
||||
try:
|
||||
event = event_queue.get(timeout=1)
|
||||
|
@ -50,6 +83,16 @@ class wsm:
|
|||
|
||||
|
||||
def startWorkerThreads(self, app):
|
||||
"""Starts worker threads for handling WebSocket data transmission.
|
||||
|
||||
This method creates and starts daemon threads for transmitting modem
|
||||
events, state updates, and FFT data to connected WebSocket clients.
|
||||
Each thread uses the transmit_sock_data_worker method to send data
|
||||
from the respective queues to the appropriate client lists.
|
||||
|
||||
Args:
|
||||
app: The main application object containing the event queues and client lists.
|
||||
"""
|
||||
self.events_thread = threading.Thread(target=self.transmit_sock_data_worker, daemon=True, args=(self.events_client_list, app.modem_events))
|
||||
self.events_thread.start()
|
||||
|
||||
|
@ -60,6 +103,12 @@ class wsm:
|
|||
self.fft_thread.start()
|
||||
|
||||
def shutdown(self):
|
||||
"""Shuts down the WebSocket manager.
|
||||
|
||||
This method sets the shutdown flag, waits for worker threads to
|
||||
finish, and logs the shutdown process. It ensures a clean shutdown
|
||||
of WebSocket connections and related threads.
|
||||
"""
|
||||
self.log.warning("[SHUTDOWN] closing websockets...")
|
||||
self.shutdown_flag.set()
|
||||
self.events_thread.join(0.5)
|
||||
|
|
Loading…
Reference in New Issue