mirror of
https://github.com/DJ2LS/FreeDATA
synced 2024-05-14 08:04:33 +00:00
198 lines
8 KiB
Python
198 lines
8 KiB
Python
import configparser
|
|
import structlog
|
|
import json
|
|
|
|
class CONFIG:
|
|
"""
|
|
CONFIG class for handling with config files
|
|
|
|
"""
|
|
|
|
def __init__(self, configfile: str):
|
|
# set up logger
|
|
self.log = structlog.get_logger("CONFIG")
|
|
|
|
# init configparser
|
|
self.config = configparser.ConfigParser(inline_comment_prefixes="#", allow_no_value=True)
|
|
|
|
try:
|
|
self.config_name = configfile
|
|
except Exception:
|
|
self.config_name = "config.ini"
|
|
|
|
self.log.info("[CFG] config init", file=self.config_name)
|
|
|
|
# check if config file exists
|
|
self.config_exists()
|
|
|
|
def config_exists(self):
|
|
"""
|
|
check if config file exists
|
|
"""
|
|
try:
|
|
return bool(self.config.read(self.config_name, None))
|
|
except Exception as configerror:
|
|
self.log.error("[CFG] logfile init error", e=configerror)
|
|
return False
|
|
|
|
# Validates config data
|
|
def validate_network_settings(self, data):
|
|
if 'modemport' in data:
|
|
if not isinstance(data['modemport'], int):
|
|
raise ValueError("'modemport' in 'NETWORK' must be an integer.")
|
|
|
|
def validate_station_settings(self, data):
|
|
for setting in ['mycall', 'mygrid']:
|
|
if setting in data and not data[setting]:
|
|
raise ValueError(f"'{setting}' in 'STATION' cannot be empty.")
|
|
if 'ssid_list' in data and not isinstance(data['ssid_list'], list):
|
|
raise ValueError("'ssid_list' in 'STATION' needs to be a list.")
|
|
|
|
def validate_audio_settings(self, data):
|
|
for setting in ['input_device', 'output_device']:
|
|
if setting in data and not isinstance(data[setting], str):
|
|
raise ValueError(f"'{setting}' in 'AUDIO' must be a string.")
|
|
for setting in ['rx_audio_level', 'tx_audio_level']:
|
|
if setting in data and not isinstance(data[setting], int):
|
|
raise ValueError(f"'{setting}' in 'AUDIO' must be an integer.")
|
|
|
|
def validate_radio_settings(self, data):
|
|
if 'radioport' in data and not (data['radioport'] is None or isinstance(data['radioport'], int)):
|
|
raise ValueError("'radioport' in 'RADIO' must be None or an integer.")
|
|
|
|
def validate_tci_settings(self, data):
|
|
if 'tci_ip' in data and not isinstance(data['tci_ip'], str):
|
|
raise ValueError("'tci_ip' in 'TCI' must be a string.")
|
|
if 'tci_port' in data and not isinstance(data['tci_port'], int):
|
|
raise ValueError("'tci_port' in 'TCI' must be an integer.")
|
|
|
|
def validate_modem_settings(self, data):
|
|
for setting in ['enable_fft', 'enable_fsk', 'enable_low_bandwidth_mode', 'respond_to_cq', 'enable_scatter']:
|
|
if setting in data and not isinstance(data[setting], bool):
|
|
raise ValueError(f"'{setting}' in 'MODEM' must be a boolean.")
|
|
for setting in ['tuning_range_fmax', 'tuning_range_fmin', 'rx_buffer_size', 'tx_delay']:
|
|
if setting in data and not isinstance(data[setting], int):
|
|
raise ValueError(f"'{setting}' in 'MODEM' must be an integer.")
|
|
|
|
def validate_mesh_settings(self, data):
|
|
if 'enable_protocol' in data and not isinstance(data['enable_protocol'], bool):
|
|
raise ValueError("'enable_protocol' in 'MESH' must be a boolean.")
|
|
|
|
def validate(self, data):
|
|
for section, settings in data.items():
|
|
if section == 'NETWORK':
|
|
self.validate_network_settings(settings)
|
|
elif section == 'STATION':
|
|
self.validate_station_settings(settings)
|
|
elif section == 'AUDIO':
|
|
self.validate_audio_settings(settings)
|
|
elif section == 'RADIO':
|
|
self.validate_radio_settings(settings)
|
|
elif section == 'TCI':
|
|
self.validate_tci_settings(settings)
|
|
elif section == 'MESH':
|
|
self.validate_mesh_settings(settings)
|
|
elif section == 'MODEM':
|
|
self.validate_modem_settings(settings)
|
|
else:
|
|
self.log.warning("wrong config", section=section)
|
|
|
|
# converts values of settings from String to Value.
|
|
# For example 'False' (type String) will be converted to False (type Bool)
|
|
# This is also needed, because configparser reads configs as string
|
|
# configparser has a type conversion, but we would have to do this for every
|
|
# item. This approach is more flexible
|
|
def convert_types(self, config):
|
|
for setting in config:
|
|
value = config[setting]
|
|
|
|
if isinstance(value, dict):
|
|
# If the value is a dictionary, apply the function recursively
|
|
config[setting] = self.convert_types(value)
|
|
|
|
elif isinstance(value, list):
|
|
# If the value is a list, iterate through the list
|
|
new_list = []
|
|
for item in value:
|
|
# Apply the function to each dictionary item in the list
|
|
if isinstance(item, dict):
|
|
new_list.append(self.convert_types(item))
|
|
else:
|
|
new_list.append(item)
|
|
config[setting] = new_list
|
|
|
|
elif isinstance(value, str):
|
|
# Attempt to convert string values
|
|
if value.lstrip('-').isdigit():
|
|
config[setting] = int(value)
|
|
else:
|
|
try:
|
|
# Try converting to a float
|
|
float_value = float(value)
|
|
# If it's actually an integer (like -50.0), convert it to an integer
|
|
config[setting] = int(float_value) if float_value.is_integer() else float_value
|
|
except ValueError:
|
|
# Convert to boolean if applicable
|
|
if value.lower() in ['true', 'false']:
|
|
config[setting] = value.lower() == 'true'
|
|
|
|
return config
|
|
|
|
# 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
|
|
# TODO check if we can include this in function "convert_types"
|
|
def handle_setting(self, section, setting, value, is_writing = False):
|
|
if (section == 'STATION' and setting == 'ssid_list'):
|
|
if (is_writing):
|
|
return json.dumps(value)
|
|
else:
|
|
return json.loads(value)
|
|
else:
|
|
return value
|
|
|
|
# Sets and writes config data from a dict containing data settings
|
|
def write(self, data):
|
|
# convert datatypes
|
|
data = self.convert_types(data)
|
|
# Validate config data before writing
|
|
self.validate(data)
|
|
|
|
for section in data:
|
|
# init section if it doesn't exist yet
|
|
if not section.upper() in self.config.keys():
|
|
self.config[section] = {}
|
|
|
|
for setting in data[section]:
|
|
new_value = self.handle_setting(
|
|
section, setting, data[section][setting], True)
|
|
self.config[section][setting] = str(new_value)
|
|
|
|
# Write config data to file
|
|
try:
|
|
with open(self.config_name, 'w') as configfile:
|
|
self.config.write(configfile)
|
|
return self.read()
|
|
except Exception as conferror:
|
|
self.log.error("[CFG] reading logfile", e=conferror)
|
|
return False
|
|
|
|
def read(self):
|
|
"""
|
|
read config file
|
|
"""
|
|
self.log.info("[CFG] reading...")
|
|
if not self.config_exists():
|
|
return False
|
|
|
|
# at first just copy the config as read from file
|
|
result = {s:dict(self.config.items(s)) for s in self.config.sections()}
|
|
result = self.convert_types(result)
|
|
|
|
# handle the special settings (like 'ssid_list')
|
|
for section in result:
|
|
for setting in result[section]:
|
|
result[section][setting] = self.handle_setting(
|
|
section, setting, result[section][setting], False)
|
|
|
|
return result
|