diff --git a/data/dbus-policy/com.revolutionpi.middleware1.conf b/data/dbus-policy/com.revolutionpi.middleware1.conf index 0118123..5645f96 100644 --- a/data/dbus-policy/com.revolutionpi.middleware1.conf +++ b/data/dbus-policy/com.revolutionpi.middleware1.conf @@ -1,4 +1,4 @@ - + @@ -12,7 +12,7 @@ + send_interface="com.revolutionpi.middleware1.PiControl"/> diff --git a/src/revpi_middleware/dbus_middleware1/system_config/revpi_config.py b/src/revpi_middleware/dbus_middleware1/system_config/revpi_config.py index 23d6378..00c809d 100644 --- a/src/revpi_middleware/dbus_middleware1/system_config/revpi_config.py +++ b/src/revpi_middleware/dbus_middleware1/system_config/revpi_config.py @@ -26,6 +26,21 @@ CONFIG_TXT_LOCATIONS = ("/boot/firmware/config.txt", "/boot/config.txt") class ComputeModuleTypes(IntEnum): + """ + Enumeration class to represent compute module types. + + This class is an enumeration that defines various types of compute + modules and assigns them associated integer values for identifying + different module types. + + Attributes: + UNKNOWN (int): Represents an unknown or undefined compute module type. + CM1 (int): Represents a Compute Module 1. + CM3 (int): Represents a Compute Module 3. + CM4 (int): Represents a Compute Module 4. + CM4S (int): Represents a Compute Module 4S. + CM5 (int): Represents a Compute Module 5. + """ UNKNOWN = 0 CM1 = 6 CM3 = 10 @@ -35,6 +50,13 @@ class ComputeModuleTypes(IntEnum): class ConfigActions(Enum): + """ + Enumeration class for defining configuration actions. + + This enumeration provides predefined constants for common configuration + actions. It can be used to ensure consistency when working with or defining + such actions in a system. + """ ENABLE = "enable" DISABLE = "disable" STATUS = "status" @@ -42,6 +64,20 @@ class ConfigActions(Enum): class RevPiConfig: + """ + Represents the configuration and hardware details of a Revolution Pi system. + + This class provides methods and properties to initialize and fetch + information related to the Revolution Pi device, such as model, serial + number, compute module type, WLAN capability, and the presence of a + connection bridge. The class works by parsing system-level files (e.g., + `/proc/cpuinfo`) and using this data to identify hardware characteristics + and features. + + Attributes: + serial (str): The serial number of the Revolution Pi device. + model (str): The model name of the Revolution Pi device. + """ def __init__(self): self._cm_type = ComputeModuleTypes.UNKNOWN @@ -55,6 +91,29 @@ class RevPiConfig: self._init_device_info() def _init_device_info(self): + """ + Initialize and retrieve detailed hardware information, including CPU details, + device type, WLAN interface, and connectivity features. + + This method gathers information from system files and other sources to + initialize device-specific attributes such as model, serial number, + compute module type, and optional features like integrated WLAN + or ConBridge support. It performs checks specific to the detected + module type to accurately populate necessary device details. + + Attributes + ---------- + model : str + The model of the CPU based on information from /proc/cpuinfo. + serial : str + The serial number extracted from /proc/cpuinfo. + _cm_type : ComputeModuleTypes, optional + The type of the compute module derived from the hardware revision value. + _wlan_class_path : str, optional + Filesystem path to the detected WLAN interface, if any. + _revpi_with_con_bridge : bool + Indicates whether the device supports the ConBridge feature. + """ dc_cpuinfo = {} # Extract CPU information @@ -103,22 +162,75 @@ class RevPiConfig: @property def class_path_wlan(self) -> str: + """ + Provides access to the WLAN class path. + + This property retrieves the stored WLAN class path, allowing the user to access it when + needed. + + Returns: + str: The WLAN class path. + """ return self._wlan_class_path @property def cm_type(self) -> ComputeModuleTypes: + """ + Gets the type of the compute module. + + The property provides access to the type of the compute + module used. The type is represented as an instance of + the `ComputeModuleTypes` class. + + Returns + ------- + ComputeModuleTypes + The type of the compute module. + """ return self._cm_type @property def with_con_bridge(self) -> bool: + """ + Indicates if the device is configured with a connection bridge. + + This property checks the internal status and determines whether the device setup + includes a connection bridge functionality. It is read-only. + + Returns: + bool: True if the connection bridge is configured, False otherwise. + """ return self._revpi_with_con_bridge @property def with_wlan(self) -> bool: + """ + Checks if WLAN is available. + + This property evaluates whether WLAN is enabled or available by checking + the presence or value of the internal attribute `_wlan_class_path`. + + Returns: + bool: True if WLAN is available, False otherwise. + """ return bool(self._wlan_class_path) class ConfigTxt: + """ + Configuration file handler for managing 'config.txt'. + + This class provides an interface to read, modify, save, and reload + Raspbian's configuration file `config.txt`. It includes functionalities + to manipulate specific parameters within the configuration and supports + managing dtoverlay and dtparam entries. The primary aim of this class + is to abstract file operations and make modifications user-friendly. + + Attributes: + _config_txt_path (str): The path to the configuration file `config.txt`. + _config_txt_lines (list[str]): Contains all lines of the configuration + file as a list of strings, where each string represents a line. + """ re_name_value = re.compile(r"^\s*(?!#)(?P[^=\s].+?)\s*=\s*(?P\S+)\s*$") def __init__(self): @@ -134,6 +246,24 @@ class ConfigTxt: self._config_txt_lines = [] def _clear_name_values(self, name: str, values: str or list) -> int: + """ + Removes all occurrences of specified name-value pairs from the configuration. + + This method searches for all name-value pairs in the configuration and + removes those that match the given name and value(s). It returns the + number of occurrences removed. + + Arguments: + name: str + The name of the configuration variable to search for. + values: str or list + The value or list of values to match the configuration variable + against. + + Returns: + int: The number of name-value pairs removed from the configuration. + + """ counter = 0 if type(values) is str: values = [values] @@ -146,6 +276,21 @@ class ConfigTxt: return counter def _get_all_name_values(self) -> List[ConfigVariable]: + """ + Retrieves all name-value pairs from the configuration text lines. + + This method parses the configuration text lines to extract all name-value + pairs. If the configuration text lines are not loaded, it reloads the + configuration before processing. Each extracted name-value pair is added to a + list as a ConfigVariable object, which also holds the index of the match in + the text lines. The method returns the compiled list of these ConfigVariable + objects. + + Returns: + List[ConfigVariable]: A list of ConfigVariable objects representing the + name-value pairs found in the configuration text lines, along with their + corresponding indexes. + """ if not self._config_txt_lines: self.reload_config() @@ -159,10 +304,28 @@ class ConfigTxt: return lst_return def reload_config(self): + """ + Reloads the configuration file and updates the list of configuration lines. + + This method reads the content of the configuration file specified by the + attribute `_config_txt_path` and updates `_config_txt_lines` with the file + contents as a list of strings, where each string represents a line. + + Returns: + None + """ with open(self._config_txt_path, "r") as f: self._config_txt_lines = f.readlines() def save_config(self): + """ + Saves the current configuration to a file. The method ensures atomicity by first writing + to a temporary file and then moving it to the desired path. After the configuration is + saved, the internal list of configuration lines is cleared. + + Raises: + OSError: If there is an issue writing to or moving the file. + """ if not self._config_txt_lines: return @@ -174,6 +337,20 @@ class ConfigTxt: self._config_txt_lines.clear() def add_name_value(self, name: str, value: str): + """ + Adds a name-value pair to the configuration if it does not already exist. + + This method checks if the given name-value pair is already present in + the configuration. If it is not present, the pair is appended to the + configuration text lines. + + Parameters: + name (str): The name to be added to the configuration. + value (str): The value corresponding to the name to be added. + + Returns: + None + """ # Check weather name and value already exists for config_var in self._get_all_name_values(): if config_var.name == name and config_var.value == value: @@ -182,12 +359,55 @@ class ConfigTxt: self._config_txt_lines.append(f"{name}={value}\n") def clear_dtoverlays(self, dtoverlays: str or list) -> int: + """ + Clears the specified device tree overlays. This method removes one or more + device tree overlays by clearing their corresponding name-value pairs. + + Args: + dtoverlays (str or list): A device tree overlay name as a string, or a + list of such overlay names to be cleared. + + Returns: + int: The number of device tree overlay name-value pairs successfully + cleared. + """ return self._clear_name_values("dtoverlay", dtoverlays) def clear_dtparams(self, dtparams: str or list) -> int: + """ + Clears the specified device tree parameters. + + This method removes the given device tree parameters by utilizing + the underlying `_clear_name_values` function with a predefined + parameter type. + + Parameters: + dtparams: str or list + A string or list of strings specifying the device tree + parameters to remove. + + Returns: + int + The number of parameters cleared. + """ return self._clear_name_values("dtparam", dtparams) def get_values(self, var_name: str) -> list: + """ + Get all values associated with a given variable name. + + This method retrieves a list of values corresponding to the specified + variable name by iterating through a collection of configuration + variables. Each configuration variable is checked for a matching name, + and its value is appended to the resulting list if a match is found. + + Parameters: + var_name (str): The name of the variable for which values are to + be retrieved. + + Returns: + list: A list of values associated with the specified variable name. + """ var_values = [] for config_var in self._get_all_name_values(): @@ -198,6 +418,16 @@ class ConfigTxt: @property def config_txt_path(self) -> str: + """ + Get the file path for the configuration text file. + + This property provides access to the private attribute `_config_txt_path` which + stores the file path to the configuration text file. + + Returns: + str + The file path to the configuration text file. + """ return self._config_txt_path @@ -324,7 +554,11 @@ def configure_gui(action: ConfigActions): return gui_available bus = SystemBus() - systemd_manager = bus.get(".systemd1") + systemd = bus.get( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + ) + systemd_manager = systemd["org.freedesktop.systemd1.Manager"] if action is ConfigActions.ENABLE: systemd_manager.SetDefaultTarget("graphical.target", True) @@ -373,6 +607,23 @@ def configure_wlan(action: ConfigActions): def get_rfkill_index(device_class_path: str) -> Optional[int]: + """ + Get the rfkill index for a device under a specific device class path. + + This function searches for and extracts the rfkill index associated with + devices located under the given device class path. It uses a regular + expression to identify and parse the rfkill index from the paths + of matching rfkill device files. + + Parameters: + device_class_path: str + The path to the device class directory where rfkill entries + are located. + + Returns: + Optional[int]: + The index of the rfkill device if found, otherwise None. + """ re_rfkill_index = re.compile(r"^/.+/rfkill(?P\d+)$") for rfkill_path in glob(join(device_class_path, "rfkill*")): match_index = re_rfkill_index.match(rfkill_path) @@ -383,8 +634,35 @@ def get_rfkill_index(device_class_path: str) -> Optional[int]: def simple_systemd(action: ConfigActions, unit: str): + """ + Performs specified actions on systemd units. + + This function allows interaction with systemd units for various operations + such as enabling, disabling, checking the status, and verifying availability. + It communicates with the systemd manager via the SystemBus and handles units + based on the action specified. + + Parameters: + action (ConfigActions): Specifies the action to be performed on the + systemd unit. Supported actions include ENABLE, + DISABLE, STATUS, and AVAILABLE. + unit (str): The name of the systemd unit on which the action is to be + performed. + + Returns: + bool: For STATUS and AVAILABLE actions, returns True if the corresponding + criteria are met (e.g., enabled and active for STATUS, or not found + for AVAILABLE). Otherwise, returns False. + + Raises: + ValueError: If the specified action is not supported. + """ bus = SystemBus() - systemd_manager = bus.get(".systemd1") + systemd = bus.get( + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + ) + systemd_manager = systemd["org.freedesktop.systemd1.Manager"] if action is ConfigActions.ENABLE: systemd_manager.UnmaskUnitFiles([unit], False) @@ -398,7 +676,7 @@ def simple_systemd(action: ConfigActions, unit: str): elif action is ConfigActions.STATUS: try: unit_path = systemd_manager.LoadUnit(unit) - properties = bus.get(".systemd1", unit_path) + properties = bus.get("org.freedesktop.systemd1", unit_path) except Exception: log.warning(f"could not get systemd unit {unit}") return False @@ -408,7 +686,7 @@ def simple_systemd(action: ConfigActions, unit: str): elif action is ConfigActions.AVAILABLE: try: unit_path = systemd_manager.LoadUnit(unit) - properties = bus.get(".systemd1", unit_path) + properties = bus.get("org.freedesktop.systemd1", unit_path) except Exception: log.warning(f"could not get systemd unit {unit}") return False