I'm writing an App on CODESYS that has a set of alarms in a variable list whose state I'd like to check in a program. Right now I'm just referencing those variables individually in the program, but I was wondering if there's a way to iterate through every variable in that list, in such a way that I could add new alarms without having to modify the program.
Any ideas?
Thanks
You can't. You may think that taking the address of the first variable and looping until you reach the address of the last would work, but here's an experiment:
{attribute 'qualified_only'}
// GVL_ALARMS
VAR_GLOBAL
START_MARKER: BYTE := 254;
alarm1: BYTE;
alarm2: BYTE;
alarm3: BYTE;
END_MARKER: BYTE := 255;
END_VAR
PROGRAM Main
VAR
ptr: POINTER TO BYTE;
alarm_ptr: POINTER TO BOOL;
alarm: BOOL;
activeAlarmsCount: DINT;
END_VAR
activeAlarmsCount := 0;
ptr := ADR(GVL_ALARMS.START_MARKER);
ptr := ADR(ptr[1]);
WHILE (ptr^ <> GVL_ALARMS.END_MARKER) DO
alarm_ptr := ptr;
alarm := alarm_ptr^;
// do whatever with alarm
IF (alarm) THEN
activeAlarmsCount := activeAlarmsCount + 1;
END_IF
ptr := ADR(ptr[1]);
END_WHILE
However the above does not work at all, at least not on my machine. And that's the problem, CODESYS gives no guarantees how the variables will be arranged in memory. I run the code in a simulator, but maybe if I run on some PLC it could work, you can't rely on that.
A possible alternative would be to define your alarms in a structure and loop over that instead:
TYPE _ALARMS :
STRUCT
alarm1: BOOL;
alarm2: BOOL;
alarm3: BOOL;
END_STRUCT
END_TYPE
// GVL_ALARMS
VAR_GLOBAL
alarms: _ALARMS;
END_VAR
PROGRAM Main
VAR
alarms_siz: DINT := SIZEOF(alarms) - 1;
i: DINT;
alarm_ptr: POINTER TO BOOL;
alarm: BOOL;
activeAlarmsCount: DINT;
END_VAR
activeAlarmsCount := 0;
alarm_ptr := ADR(alarms);
FOR i := 0 TO alarms_siz DO
alarm := alarm_ptr[i];
// do whatever with alarm
IF (alarm) THEN
activeAlarmsCount := activeAlarmsCount + 1;
END_IF
END_FOR
Results of the simulation:
This works since structures generally occupy a continuous chunk of memory, however again, I can't guarantee that some PLC won't add some padding to the struct.
Personally I'd use the Python ScriptEngine API that was added to CODESYS to check for changes in the alarms and generate new functions on every save of the project. However, this requires some knowledge of Python and the API. Here's a simple demonstration of the use of the API, which finds all variables of type BOOL in a given GVL and generates a function that counts how many of them are TRUE:
from __future__ import print_function
import sys
import re
# replace GVL_ALARMS with the name of the GVL in your project
gvl_name = 'GVL_ALARMS'
gvls = projects.primary.find(gvl_name, recursive=True)
if len(gvls) == 0:
print("GVL doesn't exist")
sys.exit()
elif len(gvls) > 1:
print("more than 1 GVL found")
sys.exit();
gvl = gvls[0]
bool_variables = []
# loop through all lines, and find all bool variables defined
for i in range(gvl.textual_declaration.linecount):
line = gvl.textual_declaration.get_line(i)
# regex pattern that searches for a BOOL variable declaration, that may have an initial value
match = re.search('\\s*(.+)\\s*:\\s*BOOL(?:\\s*:=\\s*.+)?;', line)
if match and match.group(1):
bool_variables.append(match.group(1))
# print("found bool variables: ", bool_variables)
# replace CountActiveAlarms with the name of the desired function in your project
count_true_function_name = 'CountActiveAlarms'
count_true_functions = projects.primary.find(count_true_function_name, recursive=True)
if len(count_true_functions) > 1:
print("more than 1 function found")
sys.exit();
elif len(count_true_functions) == 0:
count_true_function = projects.primary.create_pou(
name=count_true_function_name,
type=PouType.Function,
language=ImplementationLanguages.st,
return_type='UINT'
)
count_true_function.textual_declaration.insert(0, '(* AUTO GENERATED, DO NOT MODIFY *)\n')
else:
count_true_function = count_true_functions[0]
# remove old code to replace with newly generated
count_true_function.textual_implementation.remove(0, count_true_function.textual_implementation.length)
if_statement_template = \
"""IF ({0}.{1}) THEN
\t{2} := {2} + 1;
END_IF\n"""
code = ''
# loop through all found variables and generate function code
for bool_variable in bool_variables:
code += if_statement_template.format(gvl_name, bool_variable, count_true_function_name)
# insert the generated code
count_true_function.textual_implementation.insert(0, code)
The results of running the above script is the following function:
(* AUTO GENERATED, DO NOT MODIFY *)
FUNCTION CountActiveAlarms : UINT
VAR_INPUT
END_VAR
VAR
END_VAR
IF (GVL_ALARMS.alarm1) THEN
CountActiveAlarms := CountActiveAlarms + 1;
END_IF
IF (GVL_ALARMS.alarm2) THEN
CountActiveAlarms := CountActiveAlarms + 1;
END_IF
IF (GVL_ALARMS.alarm3) THEN
CountActiveAlarms := CountActiveAlarms + 1;
END_IF
If at any point you make any changes to the GVL, running the above script will regenerate the function accordingly, so you don't need to change any part of the code that calls the function.