FreeDATA/modem/config.py

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