91 lines
4.0 KiB
Python
91 lines
4.0 KiB
Python
# processors/symbol_manager.py
|
|
import sympy
|
|
import re
|
|
|
|
class SymbolManager:
|
|
def __init__(self):
|
|
# plc_name -> py_id (e.g., '"DB".Var' -> 'v0_')
|
|
self.plc_to_py_id = {}
|
|
# py_id -> Symbol object (e.g., 'v0_' -> sympy.Symbol('v0_'))
|
|
self.py_id_to_symbol = {}
|
|
# py_id -> plc_name (e.g., 'v0_' -> '"DB".Var') - Inverse mapping
|
|
self.py_id_to_plc = {}
|
|
self.counter = 0
|
|
# Pre-define common keywords/constants to avoid mapping them
|
|
self.reserved_names = {"TRUE", "FALSE"} # Add others if needed
|
|
|
|
def _generate_py_id(self):
|
|
py_id = f"v{self.counter}_"
|
|
self.counter += 1
|
|
# Extremely unlikely collision, but check anyway
|
|
while py_id in self.py_id_to_symbol:
|
|
py_id = f"v{self.counter}_"
|
|
self.counter += 1
|
|
return py_id
|
|
|
|
def get_symbol(self, plc_var_name):
|
|
"""Gets/Creates a SymPy Symbol for a PLC variable name."""
|
|
if plc_var_name is None:
|
|
print("Warning: Attempted to get symbol for None PLC name.")
|
|
return None # Or handle error appropriately
|
|
if plc_var_name.upper() in self.reserved_names:
|
|
print(f"Warning: Attempted to create symbol for reserved name: {plc_var_name}")
|
|
return None # Or handle differently (e.g., return sympy.true/false?)
|
|
|
|
if plc_var_name not in self.plc_to_py_id:
|
|
py_id = self._generate_py_id()
|
|
self.plc_to_py_id[plc_var_name] = py_id
|
|
self.py_id_to_plc[py_id] = plc_var_name
|
|
self.py_id_to_symbol[py_id] = sympy.symbols(py_id)
|
|
# print(f"DEBUG SymbolManager: Created {py_id} -> {plc_var_name}") # Debug
|
|
else:
|
|
py_id = self.plc_to_py_id[plc_var_name]
|
|
|
|
return self.py_id_to_symbol.get(py_id)
|
|
|
|
def get_plc_name(self, py_id):
|
|
"""Gets the original PLC name from a py_id."""
|
|
return self.py_id_to_plc.get(py_id)
|
|
|
|
def get_inverse_map(self):
|
|
"""Returns the map needed for postprocessing (py_id -> plc_name)."""
|
|
return self.py_id_to_plc.copy()
|
|
|
|
# Helper function to extract PLC variable name from JSON operand info
|
|
def extract_plc_variable_name(operand_info):
|
|
if operand_info and operand_info.get("type") == "variable":
|
|
return operand_info.get("name")
|
|
elif operand_info and operand_info.get("type") == "unknown_structure":
|
|
# Handle direct memory addresses like DB960.X448.0
|
|
area = operand_info.get("Area")
|
|
block_number = operand_info.get("BlockNumber")
|
|
bit_offset = operand_info.get("BitOffset")
|
|
data_type = operand_info.get("Type")
|
|
|
|
if area and block_number and bit_offset is not None:
|
|
if area == "DB" and data_type == "Bool":
|
|
# Convert bit offset to byte and bit
|
|
byte_offset = int(bit_offset) // 8
|
|
bit_pos = int(bit_offset) % 8
|
|
return f"%DB{block_number}.DBX{byte_offset}.{bit_pos}"
|
|
elif area == "DB" and data_type in ["Word", "Int"]:
|
|
byte_offset = int(bit_offset) // 8
|
|
return f"%DB{block_number}.DBW{byte_offset}"
|
|
elif area == "DB" and data_type in ["DWord", "DInt", "Real"]:
|
|
byte_offset = int(bit_offset) // 8
|
|
return f"%DB{block_number}.DBD{byte_offset}"
|
|
else:
|
|
# Other area types (M, I, Q, etc.)
|
|
if data_type == "Bool":
|
|
byte_offset = int(bit_offset) // 8
|
|
bit_pos = int(bit_offset) % 8
|
|
return f"%{area}{byte_offset}.{bit_pos}"
|
|
else:
|
|
byte_offset = int(bit_offset) // 8
|
|
if data_type in ["Word", "Int"]:
|
|
return f"%{area}W{byte_offset}"
|
|
elif data_type in ["DWord", "DInt", "Real"]:
|
|
return f"%{area}D{byte_offset}"
|
|
else:
|
|
return f"%{area}{byte_offset}"
|
|
return None # Not a variable or info missing |