TwinCat How To Initialize Functionblock with Reference to Global Variable - plc

Hello StackOverflow Community,
i have another question regarding the TwinCat/Beckhoff/Codesys Programming Language, maybe someone is able to help me with this problem.
Here is the Problem:
I want to initalize a functionblock with a reference to some variable. (In this example a simple bool).
Hereby i want to make use of the FB_Init Method.
The Functionblock itself looks something like this:
FUNCTION_BLOCK PUBLIC FB_Ref
VAR
reftoBool : REFERENCE TO BOOL;
END_VAR
The FB_Init Method looks something like this:
METHOD FB_init : BOOL
VAR_INPUT
bInitRetains : BOOL := FALSE;
bInCopyCode : BOOL := FALSE;
reftoBoolIn : REFERENCE TO BOOL;
END_VAR
reftoBool := reftoBoolIn;
The problem is that i can't get the code to work.. i don't know what im doing wrong.
Thanks in advance...

The problem is that you need to use REF= in the body of FB_init, like so:
reftoBool REF= reftoBoolIn;
See documentation here:
https://infosys.beckhoff.com/english.php?content=../content/1033/tc3_plc_intro/136301707.html

Related

How to assign the value to be returned by a method?

I have a method which I want to be returning a value. The declaration is clear to me. But how do I assign the value to be returned inside the method implementation?
I can only think of creating an output variable and use that to propagate the value to the caller. But that is definitely not how I would expect a return value to work:
METHOD M_MyMethod : BOOL
VAR_OUT
bReturnVal : BOOL;
END_VAR
// Do some method things here.
// Then assign the return value.
bReturnVal := bWhatever;
The solution is simple:
M_MyMethod := bWhatever;
Using VAR_OUT is also usefull, if you need to return more than one value and don't want to create dedicated type :)

No matching FB_Init method found when it's in a block

Running Ecostruxure machine expert which is CodeSys 3.5
I've got the following program structure:
Main: two blocks, Init and Step0 linked by a transition.
In the vars of that main:
VAR
My_Encoder : ENC_REF_M262;
...
END_VAR
So then in my Init block I have My_Encoder.FB_Init(...)
But the error message I get points to the variable declaration in Main which says "C0138 No Matching FB_Init method found fo this instantiation of ENC_REF_M262".
Try using the no_init attribute above your instantiated FB.
VAR
{ attribute 'no_init'}
My_Encoder : ENC_REF_M262;
...
END_VAR
Whenever a function block is instantiated, a matching implementation of the FB_Init method is expected to occur (even inside a wrapping fb).
N.B. You will need to explicitly run the init code (My_Encoder.fb_Init()) somewhere if it has critical functionality
Using the 'no_init' attribute will fix the compile error; however, (IMHO) this may not be the best solution. First, because you'll probably want to initialize the values and secondly because calling FB_Init explicitly, is strongly discouraged. Therefore I would like to explain why the error message occurs as well as giving some insight in how to solve the error.
The error is (AFAIK) due to either one of the two reason listed below.
When you declare and initialize a POU (e.g. an FB between the VAR and END_VAR keywords) and you directly initialize a value to one of it's inputs while this input is not included as VAR_INPUT of the FB_Init of the POU.
I'll clarify this with some code:
VAR
myFunctionBlock: FB_FunctionBlock(myInput:= 1);
END_VAR
.. Above we're initializing input "myInput" of object/instance "myFunctionBlock" with the value 1 and this requires that "myInput" is included in the FB_Init of POU "FB_FunctionBlock":
METHOD FB_init : BOOL
VAR_INPUT
bInitRetains : BOOL;
bInCopyCode : BOOL;
myInput : INT; // Include the input or else you'll get the error
END_VAR
In the VAR_INPUT-block of an FB_Init you can include inputs of the POU to which the FB_Init belongs. When you do this your are required to initialize these inputs when you declare and initialize the POU. (this is probably the case in your example)
Again I'll clarify this with some code:
METHOD FB_init : BOOL
VAR_INPUT
bInitRetains : BOOL;
bInCopyCode : BOOL;
myInput : INT;
END_VAR
.. Above input "myInput" is included in the FB_Init of POU "FB_FunctionBlock" and therefore when we create an object/instance of POU "FB_FunctionBlock" then we're required to initialize the input "myInput".
VAR
myFunctionBlock: FB_FunctionBlock(myInput:= 1);
// Include "myInput:= 1" or else you'll get the error
END_VAR

Prevent local variable retention

Suppose I have a function block POU1 which has local variables val1: INT and val2: INT, e.g.
FUNCTION_BLOCK POU1
VAR
val1: INT := 1;
val2: INT := 1;
END_VAR
Now suppose the user of the FB declares it as RETAIN, e.g.
VAR RETAIN
p1: POU1;
END_VAR
p1.val1 := 2;
p1.val2 := 2;
This will result in both val1 and val2 retaining the value of 2 in case of a warm reset, but what if I don't want that to happen to say val2 i.e. I want val1 to retain it's current value, but val2 to be reset in case of a warm reset (if the user declares my FB as RETAIN, otherwise I want both to reset)
How can I achieve this? (Also. same question goes for PERSISTENT)
PS. I tried {attribute 'init_on_onlchange'} and {attribute 'no_copy'} but they did nothing (maybe I used them wrong?). I also tried creating an additional FB with {attribute 'no_instance_in_retain'} and adding it as a local variable of POU1 but that resulted in a build error.
One way I just found is to implement FB_Exit explicitly and reset those variable in it:
METHOD FB_Exit: BOOL
VAR_INPUT
bInCopyCode: BOOL; // TRUE: the exit method is called in order to leave the instance which will be copied afterwards (online change).
END_VAR
val2 := 1; // reset all variables you don't want retained to their defaults
This seems to work, but not sure if this might have other consequences. e.g. Is FB_Exit called in case of a power failure?
The problem with FB_Exit or FB_Init and VAR PERSISTENT/RETAIN is that I couldn't find a consistent behaviour across platforms (Twincat/Codesys)
And yes there are cases where fb_exit is not called, for example in Twincat when you do a cold reset.
My approach on this would be different.
I would neither use attributes nor fb_exit or fb_init which under certain circumstances could be difficult to debug.
Instead I would use a simple global FB like this one:
FUNCTION_BLOCK FB_System
VAR
bInit : BOOL;
nCycleCount : UINT;
END_VAR
VAR CONSTANT
cINIT_AFTER_CYCLE : UINT := 2;
END_VAR
IF NOT bInit
THEN
nCycleCount := nCycleCount + 1;
END_IF
IF nCycleCount >= cINIT_AFTER_CYCLE
THEN
bInit := TRUE;
END_IF
METHOD isInit : BOOL
isInit := bInit;
Now, add an input to your retain/persistent FB:
VAR_INPUT
bSystemInit : BOOL;
END_VAR
And call it like this:
fbRetain(bSystemInit := fbSystem.isInit());
Initialize your values if the system is not initialized.
Add this check in your FB implementation:
IF NOT bSystemInit THEN
anIntVar := 0;
//or call a reset() method where you put all your variables that need to be initialized
END_IF
If you have many FBs that need this kind of initialization, you can extend them with an FB that has this example code in it.
By doing so you can reuse your code efficiently.
Having said that, I must warn you that I had many problems with persistent data in the past.
It happened to me repeatedly that persistent data became corrupt causing production problems or even preventing the runtime to start.
If I had to design a system from scratch I would use the XML-server from Beckhoff or the XML-utility from codesys to store relevant production data in an xml-file and retrieve this data at runtime start.

Array as Type for Interface Property CoDeSys

I am attempting to implement the Observer design pattern in CoDeSys using structured text. I'm encountering an error where I can't call the ., [], nor [..] on an interface property. Is it possible to have an interface property that is an array? The interface property in question is the following:
PROPERTY observers : ARRAY[0..20] OF IObserver
And the interface structure follows the observer patter like the following.
The implementation of the notifyAllObservers method is the following.
METHOD notifyAllObservers
VAR
i : INT;
END_VAR
FOR i := 0 TO 20 DO
CommandHandler.observers[i].update(CommandHandler.commands);
END_FOR
I think the problem with your CommandHandler.observers[i] is that you don't have any instances of anything implementing the IObserver.
You cannot access your observers property the way you do since it is not an object.
If you delete the observers property, define an observer FB implementing the IObserver and then add an array of observer (FB) inside CommandHandler your notifyAllObservers code will work.
You can't access the array fields that way.
There are several other ways you can achieve what you want in Structured Text:
Since your property is public there is no reason not to declare the array of IObserver as an input field of the Function Block.
If you do so you can access it the way you desire.
Declaration part:
FUNCTION_BLOCK CommandHandler
VAR_INPUT
IObservers : ARRAY[0..19] OF IObserver;
END_VAR
Example call:
fbCommandHandler.IObservers[i].update();
Another way is you access (and modifiy, etc..) your IObservers from inside the update method:
Create a new Interface IObserverArray with an update method:
METHOD update : BOOL
VAR_INPUT
index : INT;
cmd : INT;
END_VAR
Create a new Function Block implementing the IObserverArray itf:
FUNCTION_BLOCK ObserverArray IMPLEMENTS IObserverArray
//declaration part of the update method
METHOD update : BOOL
VAR_INPUT
index : INT;
cmd : INT;
END_VAR
VAR
itfObservers : ARRAY [0..19] OF IObserver;
END_VAR
//imlementation part of the update method
itfObservers[index].update(cmd);
Create a method getObservers() in your CommandHandler Function Block that returns a IObserverArray:
METHOD getObservers : IObserverArray
//imlementation part of the getObservers method
getObservers := aObservers;
Now you just declare aObservers : ObserverArray; in your CommandHandler as a VAR
and call it like this : fbCommandHandler.getObservers().update(5,12);
https://help.codesys.com/api-content/2/codesys/3.5.12.0/en/_cds_operator_new/
_new(8,nLength +1)
Make a list with memcpy(new,old)
__DELETE(old);
Something like that should work!
How did you set up the property observers?
If you just got an array of objects you are not able to access 1 of the array members individually. However if you only use a get action and instead of getting the array of observers, get a reference to an array of observers it will work. like so
PROPERTY PUBLIC observers : REFERENCE TO ARRAY[*Lower_Bound*..*Upper_Bound*] OF *Class to be returned*
In the get action set the following:
observers REF= *object to be returned*
In this way the getter wil return a pointer/reference instead of the object. With the reference you can reach for an individual item in the array.
Hope this helps

What are the semantics of input variables passed by reference?

Beckhoff's TwinCat-3, as well as Codesys 3 it's based on, adds references as an extension to IEC-61131-3 languages. I'm wondering what is the exact grammar and semantics of this non-standard addition (that's the problem with them: nobody bothers documenting them as well as a standard would).
It the following F_IsNonEmpty function valid and doing what one would expect, when invoked from the F_Test test function below?
FUNCTION F_IsNonEmpty : BOOL
VAR_INPUT
text : REFERENCE TO STRING;
END_VAR
F_IsNonEmpty := LEN(text) > 0;
END_FUNCTION
FUNCTION F_Test1
VAR
testMessage : STRING := '123';
END_VAR
IF F_IsNonEmpty(text := testMessage) THEN
{...}
END_IF
END_FUNCTION
Given that the target of the reference must be initialized using the REF= operator (v.s. e.g. C++ where reference targets are immutable), I'd have expected that the following invoking code would be correct instead - but it doesn't even compile:
FUNCTION F_Test2
VAR
testMessage : STRING := '123';
END_VAR
IF F_IsNonEmpty(text REF= testMessage) THEN
{...}
END_IF
END_FUNCTION
It seems that F_Test1 works correctly, but I'd like someone who actually uses Codesys 3 or TwinCat-3 REFERENCE TO feature to confirm.
When you use a REFERENCE in a VAR_INPUT, it's as if you were using a VAR_IN_OUT variable.
Otherwise if you declare your REFERENCE in the VAR section, you need to use REF= when assigning another variable to it (or get an exception).
In essence, REFERENCE (like a VAR_IN_OUT var) is a more convenient and "safe" pointer because the dereference operator ^ is not needed and because the type is checked at compile time.