first run of docstring session....

pull/912/head
DJ2LS 2025-03-12 11:54:09 +01:00
parent 0de64c6a03
commit e6b3e83790
4 changed files with 274 additions and 13 deletions

View File

@ -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():

View File

@ -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)

View File

@ -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"

View File

@ -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)