ParamManagerScripts/backend/script_groups/S7_DB_Utils/.doc/S7 Parser Utilities - Techn...

11 KiB

S7 Parser Utilities - Technical Documentation

Table of Contents

  1. Introduction
  2. Utility Functions
  3. 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

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

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

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

# 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

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

# 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

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

# 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

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

# 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

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

# 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

# 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

# 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')}")