Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions GlueGenerator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import os
import re
from jinja2 import Environment, FileSystemLoader
from util import paths

# LOCATED_VARIABLES.h example:
# __LOCATED_VAR(BOOL,__QX0_0,Q,X,0,0)
# __LOCATED_VAR(INT,__QW0,Q,W,0)
# __LOCATED_VAR(BOOL,__QX0_1,Q,X,0,1)

class GlueGenerator:

def __init__(self):
self.__loader = FileSystemLoader(os.path.join(paths.AbsDir(__file__), "templates"))

def __glue_logic(self, varName):
"""
Generate glue logic based on variable type.
"""

# Extract indices
print(f"Linking variable {varName}")
try:
parts = varName.split("_")
pos1 = int(parts[2][2:]) # number after QX0 or QW0
pos2 = int(parts[3]) if len(parts) > 3 else 0
except Exception as e:
raise Exception(f"Error parsing variable name '{varName}': {e}")

kind = varName[2] # I, Q, M
sub = varName[3] # X, B, W, D, L

if kind == 'I':
if sub == 'X':
return f"bool_input_ptr[{pos1}][{pos2}] = (IEC_BOOL *){varName};"
elif sub == 'B':
return f"byte_input_ptr[{pos1}] = (IEC_BYTE *){varName};"
elif sub == 'W':
return f"int_input_ptr[{pos1}] = (IEC_UINT *){varName};"
elif sub == 'D':
return f"dint_input_ptr[{pos1}] = (IEC_UDINT *){varName};"
elif sub == 'L':
return f"lint_input_ptr[{pos1}] = (IEC_ULINT *){varName};"

elif kind == 'Q':
if sub == 'X':
return f"bool_output_ptr[{pos1}][{pos2}] = (IEC_BOOL *){varName};"
elif sub == 'B':
return f"byte_output_ptr[{pos1}] = (IEC_BYTE *){varName};"
elif sub == 'W':
return f"int_output_ptr[{pos1}] = (IEC_UINT *){varName};"
elif sub == 'D':
return f"dint_output_ptr[{pos1}] = (IEC_UDINT *){varName};"
elif sub == 'L':
return f"lint_output_ptr[{pos1}] = (IEC_ULINT *){varName};"

elif kind == 'M':
if sub == 'W':
return f"int_memory_ptr[{pos1}] = (IEC_UINT *){varName};"
elif sub == 'D':
return f"dint_memory_ptr[{pos1}] = (IEC_UDINT *){varName};"
elif sub == 'L':
return f"lint_memory_ptr[{pos1}] = (IEC_ULINT *){varName};"

raise Exception(f"Unhandled variable type: {varName}")

def __parse_line(self, line):
"""
Parse a line from LOCATED_VARIABLES.h to extract variable information.
Example: __LOCATED_VAR(BOOL,__QX0_0,Q,X,0,0)
"""
m = re.match(r"__LOCATED_VAR\(([^,]+),([^,]+),.*\)", line.strip())
if not m:
print(f"Warning: Line '{line.strip()}' does not match expected format.")
return None
varType, varName = m.group(1), m.group(2)
return {"type": varType, "name": varName, "glue_code": self.__glue_logic(varName)}

def generate_glue_variables(self, located_vars_lines):
"""
Generate glue variables from the LOCATED_VARIABLES content.
"""
parsed = []
for line in located_vars_lines:
entry = self.__parse_line(line)
if entry is not None:
parsed.append(entry)

env = Environment(loader=self.__loader)
template = env.get_template("glueVars.c.j2")
return template.render(vars=parsed)
135 changes: 135 additions & 0 deletions templates/glueVars.c.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@

/********************************************************************************
* Copyright (C) 2025 Autonomy
*
* This file is generated by xml2st.
* Do not edit this file directly.
* If you want to change the content, edit the jinja2 template
* or the source PLC program and regenerate.
********************************************************************************/

#include "iec_std_lib.h"

#define __LOCATED_VAR(type, name, ...) type __##name;
#include "LOCATED_VARIABLES.h"
#undef __LOCATED_VAR
#define __LOCATED_VAR(type, name, ...) type* name = &__##name;
#include "LOCATED_VARIABLES.h"
#undef __LOCATED_VAR

TIME __CURRENT_TIME;
BOOL __DEBUG;
extern unsigned long long common_ticktime__;

#ifdef ARDUINO

//OpenPLC Buffers
#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__) || defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega16U4__)

#define MAX_DIGITAL_INPUT 8
#define MAX_DIGITAL_OUTPUT 32
#define MAX_ANALOG_INPUT 6
#define MAX_ANALOG_OUTPUT 32
#define MAX_MEMORY_WORD 0
#define MAX_MEMORY_DWORD 0
#define MAX_MEMORY_LWORD 0

IEC_BOOL *bool_input[MAX_DIGITAL_INPUT/8][8];
IEC_BOOL *bool_output[MAX_DIGITAL_OUTPUT/8][8];
IEC_UINT *int_input[MAX_ANALOG_INPUT];
IEC_UINT *int_output[MAX_ANALOG_OUTPUT];

#else

#define MAX_DIGITAL_INPUT 56
#define MAX_DIGITAL_OUTPUT 56
#define MAX_ANALOG_INPUT 32
#define MAX_ANALOG_OUTPUT 32
#define MAX_MEMORY_WORD 20
#define MAX_MEMORY_DWORD 20
#define MAX_MEMORY_LWORD 20

IEC_BOOL *bool_input[MAX_DIGITAL_INPUT/8][8];
IEC_BOOL *bool_output[MAX_DIGITAL_OUTPUT/8][8];
IEC_UINT *int_input[MAX_ANALOG_INPUT];
IEC_UINT *int_output[MAX_ANALOG_OUTPUT];
IEC_UINT *int_memory[MAX_MEMORY_WORD];
IEC_UDINT *dint_memory[MAX_MEMORY_DWORD];
IEC_ULINT *lint_memory[MAX_MEMORY_LWORD];

#endif

#else

#define BUFFER_SIZE 1024

//Internal buffers for I/O and memory. These buffers are defined in the
//main program

//Booleans
static IEC_BOOL *(*bool_input_ptr)[8] = NULL;
static IEC_BOOL *(*bool_output_ptr)[8] = NULL;

//Bytes
static IEC_BYTE *(*byte_input_ptr) = NULL;
static IEC_BYTE *(*byte_output_ptr) = NULL;

//Analog I/O
static IEC_UINT *(*int_input_ptr) = NULL;
static IEC_UINT *(*int_output_ptr) = NULL;

//32bit I/O
static IEC_UDINT *(*dint_input_ptr) = NULL;
static IEC_UDINT *(*dint_output_ptr) = NULL;

//64bit I/O
static IEC_ULINT *(*lint_input_ptr) = NULL;
static IEC_ULINT *(*lint_output_ptr) = NULL;

//Memory
static IEC_UINT *(*int_memory_ptr) = NULL;
static IEC_UDINT *(*dint_memory_ptr) = NULL;
static IEC_ULINT *(*lint_memory_ptr) = NULL;

void setBufferPointers(IEC_BOOL *input_bool[BUFFER_SIZE][8], IEC_BOOL *output_bool[BUFFER_SIZE][8],
IEC_BYTE *input_byte[BUFFER_SIZE], IEC_BYTE *output_byte[BUFFER_SIZE],
IEC_UINT *input_int[BUFFER_SIZE], IEC_UINT *output_int[BUFFER_SIZE],
IEC_UDINT *input_dint[BUFFER_SIZE], IEC_UDINT *output_dint[BUFFER_SIZE],
IEC_ULINT *input_lint[BUFFER_SIZE], IEC_ULINT *output_lint[BUFFER_SIZE],
IEC_UINT *int_memory[BUFFER_SIZE], IEC_UDINT *dint_memory[BUFFER_SIZE],
IEC_ULINT *lint_memory[BUFFER_SIZE])
{
bool_input_ptr = input_bool;
bool_output_ptr = output_bool;
byte_input_ptr = input_byte;
byte_output_ptr = output_byte;
int_input_ptr = input_int;
int_output_ptr = output_int;
dint_input_ptr = input_dint;
dint_output_ptr = output_dint;
lint_input_ptr = input_lint;
lint_output_ptr = output_lint;
int_memory_ptr = int_memory;
dint_memory_ptr = dint_memory;
lint_memory_ptr = lint_memory;
}
#endif

void glueVars()
{
{% for v in vars %}
{{ v.glue_code }}
{% endfor %}
}

void updateTime()
{
__CURRENT_TIME.tv_sec += common_ticktime__ / 1000000000ULL;
__CURRENT_TIME.tv_nsec += common_ticktime__ % 1000000000ULL;

if (__CURRENT_TIME.tv_nsec >= 1000000000ULL)
{
__CURRENT_TIME.tv_nsec -= 1000000000ULL;
__CURRENT_TIME.tv_sec += 1;
}
}
50 changes: 49 additions & 1 deletion xml2st.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from PLCControler import PLCControler
from ProjectController import ProjectController
from ComplexParser import ComplexParser
from GlueGenerator import GlueGenerator


def compile_xml_to_st(xml_file_path):
Expand Down Expand Up @@ -97,19 +98,57 @@ def append_debugger_to_st(st_file, debug_text):
f.write("\n")
f.write(c_debug)

def generate_gluevars(located_vars_file):
if not os.path.isfile(located_vars_file) or not located_vars_file.lower().endswith(".h"):
print(
f"Error: Invalid file '{located_vars_file}'. A path to a LOCATED_VARIABLES.h file is expected.",
file=sys.stderr,
)
return None

# Read the LOCATED_VARIABLES.h file
with open(located_vars_file, "r") as f:
located_vars = f.readlines()

# Create an instance of GlueGenerator
generator = GlueGenerator()
glueVars = generator.generate_glue_variables(located_vars)

if glueVars is None:
print("Error: Failed to generate glue variables.", file=sys.stderr)
return None

# Save the generated glue variables to a file
glue_vars_file = os.path.join(os.path.dirname(located_vars_file), "glueVars.c")
with open(glue_vars_file, "w") as f:
f.write(glueVars)

# Print success message
print(f"Glue variables saved to {glue_vars_file}")

def main():
parser = argparse.ArgumentParser(
description="Process a PLCopen XML file and transpiles it into a Structured Text (ST) program."
)
parser.add_argument("--generate-st", type=str, help="The path to the XML file")
parser.add_argument(
"--generate-st",
metavar=("XML_FILE"),
type=str,
help="The path to the XML file"
)
parser.add_argument(
"--generate-debug",
nargs=2,
metavar=("ST_FILE", "CSV_FILE"),
type=str,
help="Paths to the ST file and the variables CSV file",
)
parser.add_argument(
"--generate-gluevars",
metavar=("LOCATED_VARS_FILE"),
type=str,
help="The path to the LOCATED_VARIABLES.h file"
)

args = parser.parse_args()

Expand Down Expand Up @@ -149,6 +188,15 @@ def main():
print(f"Error generating debug: {e}", file=sys.stderr)
sys.exit(1)

elif args.generate_gluevars:
try:
print("Generating glue variables...")
generate_gluevars(args.generate_gluevars)

except Exception as e:
print(f"Error generating glue variables: {e}", file=sys.stderr)
sys.exit(1)

else:
print(
"Error: No valid arguments provided. Use --help for usage information.",
Expand Down