11 KiB
S7 Parser Utilities - Technical Documentation
Table of Contents
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
- Loads the configuration using
load_configuration()
- Retrieves the "working_directory" setting
- Exits with error if no working directory is specified
- 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()
frombackend.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
- Ignores
OffsetContext
objects (returnsNone
) - Converts
ArrayDimension
objects to dictionaries - Handles
VariableInfo
and other objects with__dict__
attribute - Properly manages special properties like
current_element_values
- 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
- Recursively processes the hierarchical structure of a DB/UDT
- Expands nested structures, UDT instances, and array elements
- Maintains offset information and creates full path strings
- Adds hierarchical path pointers for direct access to original structure
- Assigns element type designations based on variable characteristics
- 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 elementelement_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 offsetbit_size
(int, optional): Size in bits (>0 for BOOL types). Defaults to 0.
Returns
str
: A formatted address string in S7 notation
Behavior
- For bit variables (BOOLs), formats as "X.Y" where X is the byte and Y is the bit
- For byte-aligned variables, returns the byte number as an integer
- 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 hierarchyhierarchy_path
(List[Dict]): List of hierarchical steps to navigate
Returns
- The variable object if found,
None
if not accessible
Behavior
- Starts at the root object provided
- Follows each step in the hierarchy path
- Each step contains a type ("members" or "children") and an index
- Returns the object found at the end of the path
- 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
- Uses the UDT source name if available, otherwise uses the data type
- Adds ARRAY declarations with dimensions for array variables
- Adds size specifications for STRING types
- 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')}")