# S7 Parser Utilities - Technical Documentation ## Table of Contents 1. [Introduction](#introduction) 2. [Utility Functions](#utility-functions) - [find_working_directory](#find_working_directory) - [custom_json_serializer](#custom_json_serializer) - [flatten_db_structure](#flatten_db_structure) - [format_address_for_display](#format_address_for_display) - [access_by_hierarchy_path](#access_by_hierarchy_path) - [format_data_type_for_source](#format_data_type_for_source) 3. [Usage Examples](#usage-examples) ## Introduction This document provides technical documentation for the utility functions used in the S7 parser system. These functions facilitate working with Siemens S7 data structures, addressing, and serialization/deserialization processes. ## Utility Functions ### find_working_directory #### Purpose Retrieves the configured working directory from the script configuration. #### Signature ```python def find_working_directory() -> str: ``` #### Returns - `str`: The absolute path to the configured working directory. #### Behavior 1. Loads the configuration using `load_configuration()` 2. Retrieves the "working_directory" setting 3. Exits with error if no working directory is specified 4. Returns the working directory path #### Usage Example ```python working_dir = find_working_directory() print(f"Using working directory: {working_dir}") ``` #### Notes - This function depends on `load_configuration()` from `backend.script_utils` - The working directory should be defined in the script configuration JSON file --- ### custom_json_serializer #### Purpose Custom JSON serializer that handles S7 data structures that are not natively JSON-serializable. #### Signature ```python def custom_json_serializer(obj: Any) -> Any: ``` #### Parameters - `obj` (Any): Object to be serialized to JSON #### Returns - Serializable version of the object compatible with JSON format #### Behavior 1. Ignores `OffsetContext` objects (returns `None`) 2. Converts `ArrayDimension` objects to dictionaries 3. Handles `VariableInfo` and other objects with `__dict__` attribute 4. Properly manages special properties like `current_element_values` 5. Raises `TypeError` for objects that cannot be serialized #### Usage Example ```python # Serialize a parsed S7 object to JSON json_output = json.dumps(parsed_result, default=custom_json_serializer, indent=2) with open("output.json", "w", encoding='utf-8') as f: f.write(json_output) ``` #### Notes - Removes empty lists and None values to keep JSON output clean - Special handling for `is_udt_expanded_member` flag - Preserves format of array element values with their offsets --- ### flatten_db_structure #### Purpose Completely flattens a hierarchical DB/UDT structure, expanding all nested variables, UDTs, and array elements for easier processing. #### Signature ```python def flatten_db_structure(db_info: Dict[str, Any]) -> List[Dict[str, Any]]: ``` #### Parameters - `db_info` (Dict): The DB or UDT structure to flatten #### Returns - `List[Dict]`: A list of flattened variables with full paths and hierarchy path references #### Behavior 1. Recursively processes the hierarchical structure of a DB/UDT 2. Expands nested structures, UDT instances, and array elements 3. Maintains offset information and creates full path strings 4. Adds hierarchical path pointers for direct access to original structure 5. Assigns element type designations based on variable characteristics 6. Returns a list sorted by byte.bit offset for consistent ordering #### Key Properties Added to Each Variable - `full_path`: Complete path to the variable (e.g., `"MyStruct.MyArray[1]"`) - `is_array_element`: Boolean indicating if this is an array element - `element_type`: Type classification ("SIMPLE_VAR", "ARRAY", "ARRAY_ELEMENT", "STRUCT", "UDT_INSTANCE") - `address_display`: Formatted display version of the address - `_hierarchy_path`: Internal path references for direct access to the original structure - `_array_index`: For array elements, stores the index for direct access #### Usage Example ```python # Get a flattened view of a DB flat_variables = flatten_db_structure(my_db) # Find variables with specific element types simple_vars = [var for var in flat_variables if var.get("element_type") == "SIMPLE_VAR"] # Create an offset-to-variable map for quick lookup variables_by_offset = {var["byte_offset"]: var for var in flat_variables} ``` #### Notes - Prevents duplicate processing of expanded UDT members - Handles array elements including their specific offsets - Preserves all original attributes of each variable - Critical for functions that need to process or match variables by offset --- ### format_address_for_display #### Purpose Formats byte offsets into readable S7 address notation, correctly handling bit addresses for BOOL types. #### Signature ```python def format_address_for_display(byte_offset: float, bit_size: int = 0) -> str: ``` #### Parameters - `byte_offset` (float): The byte offset with the decimal part representing the bit offset - `bit_size` (int, optional): Size in bits (>0 for BOOL types). Defaults to 0. #### Returns - `str`: A formatted address string in S7 notation #### Behavior 1. For bit variables (BOOLs), formats as "X.Y" where X is the byte and Y is the bit 2. For byte-aligned variables, returns the byte number as an integer 3. Preserves decimal notation only when necessary #### Usage Example ```python # Format a BOOL address bool_address = format_address_for_display(12.5, 1) # Returns "12.5" # Format a regular variable address int_address = format_address_for_display(24.0, 0) # Returns "24" ``` #### Notes - Particularly important for BOOL variables and array elements - Converts integer byte offsets (e.g., 10.0) to simple integers ("10") - The bit part of BOOL addresses is rounded to account for potential floating-point imprecision --- ### access_by_hierarchy_path #### Purpose Directly accesses a variable in the hierarchical structure using the hierarchy path references created by `flatten_db_structure`. #### Signature ```python def access_by_hierarchy_path(root_obj: Dict[str, Any], hierarchy_path: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]: ``` #### Parameters - `root_obj` (Dict): The root object (usually a DB) containing the hierarchy - `hierarchy_path` (List[Dict]): List of hierarchical steps to navigate #### Returns - The variable object if found, `None` if not accessible #### Behavior 1. Starts at the root object provided 2. Follows each step in the hierarchy path 3. Each step contains a type ("members" or "children") and an index 4. Returns the object found at the end of the path 5. Returns None if any step in the path is invalid #### Usage Example ```python # Get flattened variables with hierarchy paths flat_vars = flatten_db_structure(my_db) # Find a specific variable target_var = next(var for var in flat_vars if var["full_path"] == "MyStruct.MyField") # Access the original variable directly in the hierarchy original_var = access_by_hierarchy_path(my_db, target_var["_hierarchy_path"]) # Now you can modify the original variable if original_var: original_var["current_value"] = "New Value" ``` #### Notes - Critical for updating values in the original structure - Much more efficient than manually traversing the hierarchy - Works because hierarchy paths are preserved across deep copies - Provides error checking for invalid paths --- ### format_data_type_for_source #### Purpose Formats a variable's data type information as it would appear in S7 source code. #### Signature ```python def format_data_type_for_source(var_info: Dict[str, Any]) -> str: ``` #### Parameters - `var_info` (Dict): Variable information dictionary #### Returns - `str`: Formatted data type string as it would appear in S7 source code #### Behavior 1. Uses the UDT source name if available, otherwise uses the data type 2. Adds ARRAY declarations with dimensions for array variables 3. Adds size specifications for STRING types 4. Returns the complete type declaration as it would appear in source code #### Usage Example ```python # Format a simple INT variable simple_var = {"data_type": "INT"} type_str = format_data_type_for_source(simple_var) # Returns "INT" # Format an array of REALs array_var = { "data_type": "REAL", "array_dimensions": [{"lower_bound": 1, "upper_bound": 10}] } type_str = format_data_type_for_source(array_var) # Returns "ARRAY [1..10] OF REAL" # Format a STRING variable string_var = {"data_type": "STRING", "string_length": 32} type_str = format_data_type_for_source(string_var) # Returns "STRING[32]" ``` #### Notes - Essential for generating S7 source code declarations - Handles UDT references, arrays, and string length specifications - Properly formats complex types like arrays of UDTs or strings ## Usage Examples ### Complete Example: Processing and Updating a DB ```python # Load JSON data with open("my_db.json", "r", encoding="utf-8") as f: db_data = json.load(f) # 1. Flatten the structure for easier processing flat_vars = flatten_db_structure(db_data["dbs"][0]) # 2. Display all variables with their addresses for var in flat_vars: address = var.get("address_display", format_address_for_display(var["byte_offset"], var.get("bit_size", 0))) data_type = format_data_type_for_source(var) print(f"{address}: {var['full_path']} ({data_type})") # 3. Update a specific variable in the original structure target_var = next(var for var in flat_vars if var["full_path"] == "Settings.Timeout") original_var = access_by_hierarchy_path(db_data["dbs"][0], target_var["_hierarchy_path"]) if original_var: original_var["current_value"] = "5000" # Update the value print(f"Updated {target_var['full_path']} to 5000") # 4. Save the modified structure back to JSON with open("updated_db.json", "w", encoding="utf-8") as f: json.dump(db_data, f, default=custom_json_serializer, indent=2) ``` ### Example: Creating a Filtered View of Variables ```python # Load DB data working_dir = find_working_directory() input_file = os.path.join(working_dir, "my_db.json") with open(input_file, "r", encoding="utf-8") as f: db_data = json.load(f) # Get flattened structure flat_vars = flatten_db_structure(db_data["dbs"][0]) # Filter by element type simple_vars = [var for var in flat_vars if var.get("element_type") == "SIMPLE_VAR"] array_elements = [var for var in flat_vars if var.get("element_type") == "ARRAY_ELEMENT"] structs = [var for var in flat_vars if var.get("element_type") == "STRUCT"] # Create address-based lookup for quick access vars_by_address = {} for var in flat_vars: if var.get("element_type") in ["SIMPLE_VAR", "ARRAY_ELEMENT"]: vars_by_address[var["byte_offset"]] = var # Look up a variable by address target_address = 24.0 if target_address in vars_by_address: target_var = vars_by_address[target_address] print(f"Variable at address {target_address}: {target_var['full_path']}") # Access and modify the original variable in the hierarchy original_var = access_by_hierarchy_path(db_data["dbs"][0], target_var["_hierarchy_path"]) if original_var: print(f"Current value: {original_var.get('current_value')}") ```