Casting function block - plc

I'm using Wago PFC200 for my home automation. I got base function block:
FUNCTION_BLOCK ABSTRACT Room
and two the interface:
INTERFACE IBlinds
- BlindsUp
- BlindsDown
and
ILights
-TurnOffLights
-TurnOnLights
My room's instances looks like this:
FUNCTION_BLOCK Garage EXTENDS Room IMPLEMENTS ILights, IBlinds
In my PLC_PRG I've all instances of my rooms:
PROGRAM PLC_PRG
VAR
Bedroom: Bedroom;
Garage: Garage;
Hall: Hall;
Boilerroom: Boilerroom;
...
END_VAR
Under the PLC_PRG I've some methods to e.g.: automate blids:
METHOD MoveBlindsToMorningPosition
VAR CONSTANT
siCount: SINT := 5;
END_VAR
VAR_INPUT
xMoveSignal: BOOL;
END_VAR
VAR
_siIndex: SINT;
_rooms: ARRAY[0..siCount] OF POINTER TO IBlinds := [ADR(Livingroom), ADR(Diningroom), ADR(Kitchen), ADR(Toilet), ADR(Boilerroom), ADR(Garage)];
END_VAR
FOR _siIndex := 0 TO siCount DO
_rooms[_siIndex]^.MoveBlindsToMorningPosition(xMove := xMoveSignal);
END_FOR
But I got the following compilation errors in the _rooms array: C0032: Cannot convert type 'POINTER TO Garage' to type 'POINTER TO IBlinds'
My function blocks actually implement IBlinds. Is there a way to cast function block?

First of all, an interface is already a reference to a function block:
CODESYS always treats variables declared with the type of an interface as references.
So there shouldn't be a need to use pointers.
Secondly, to cast an function block into an interface, personally I'd recommend using a dedicated method inside a function block. For example:
INTERFACE inter1
- ...
- ToInter1
INTERFACE inter2
- ...
- ToInter2
and implement them inside MyObject like:
ToInter1 := THIS^;
ToInter2 := THIS^;
And then you can:
myObj: MyObject;
i1: inter1 := myObj.ToInter1();
i2: inter2 := myObj.ToInter2();
Or
arr: ARRAY[x..y] OF inter1;
arr[z] := myObj.ToInter1();
At least this is what I do to solve this

Related

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.

Delphi AV when using TStringList in a custom class

In Delphi Rio, I have created a class whose purpose is to read a record from a database. This record is purely read only, and after reading, I need to derive some additional properties. My problem has to do with a Stringlist I am wanting to use in my class definition. I have a private class member called fVENDORS_TO_COLORCODE. This is a comma separated string. I am wanting to make a property that is a TStringlist. I am using TStringList.CommaToText to load my value into the Tstringlist. I do this in the Create Constructor. The issue I am having is that while the StringList is valid in the constructor, it is nil outside of the constructor, and I don't know what I am doing wrong. Here is the relevant portions of code.
type
TProfileDef = class(TObject)
private
fNAME: String;
fVENDORS_TO_COLORCODE: String; // incoming comma separated string. Example string: Microsoft,IBM
fVENDORS_TO_COLORCODE_SL : TStringList;
..
public
constructor Create(ProfileName: String);
destructor Destroy; override;
published
property NAME: String read fNAME;
property VENDORS_TO_COLORCODE: String read fVENDORS_TO_COLORCODE;
property VENDORS_TO_COLORCODE_SL : TStringList read fVENDORS_TO_COLORCODE_SL;
..
end;
implementation
destructor TProfileDef.Destroy;
begin
inherited;
fVENDORS_TO_COLORCODE_SL.Free;
end;
constructor TProfileDef.Create(ProfileName: String);
var
fVENDORS_SL: TStringList;
fVENDORS_TO_COLORCODE_SL: TStringList;
TempVendorList : String;
begin
inherited Create;
fName := ProfileName;
.. [Find my record based on ProfileName, and load the DB columns into the private variables]..
// Load the Color Code String into a StringList;
fVENDORS_TO_COLORCODE_SL := TStringList.Create;
fVENDORS_TO_COLORCODE_SL.CommaToText := fVENDORS_TO_COLORCODE;
end;
Within the Constructor, the fVENDORS_TO_COLORCODE_SL stringlist is created, and data is added...
The issue is when I try to use it...
var
TestClass: TProfileDef;
begin
TestClass := TProfileDef.Create('Sample Profile');
// TestClass.Name is valid
// TestClass.VENDORS_TO_COLORCODE_SL is nil, and trying to access gives AV
Somehow I am defining this wrong, but I can't determine what it is is, in order to correct it.
Your class has a private field
fVENDORS_TO_COLORCODE_SL: TStringList;
Your constructor should create a TStringList object and have this variable point to it. I assume that is your intention, at least. However, your constructor has a local variable with the same name, fVENDORS_TO_COLORCODE_SL, so the line
fVENDORS_TO_COLORCODE_SL := TStringList.Create;
indeed creates a TStringList object, but the pointer is saved to this local variable, and the class's field with the same name remains nil.
Solution: Remove the declaration of the local variable in the constructor.
// Load the Color Code String into a StringList;
fVENDORS_TO_COLORCODE_SL := TStringList.Create;
This line here in the constructor is the problem. You have two variables named fVENDORS_TO_COLORCODE_SL. One is a private member of the class declared in the private section of th class declaration, the other is a local variable declared in the var section of the constructor.
Guess which one takes precedence. That's right, the local variable in the constructor. That line initialized the local variable named fVENDORS_TO_COLORCODE_SL, the private class member with the same name is still nil.
As a general rule I preface local variables in a method with an l and only preface class members with an f to avoid just this sort of problem.
Rename your local variables in the constructor like so:
constructor TProfileDef.Create(ProfileName: String);
var
lVENDORS_SL: TStringList;
lVENDORS_TO_COLORCODE_SL: TStringList;
lTempVendorList : String;
begin
Then update your code and rebuild. Things should start to become obvious pretty quickly.

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.

Delphi: How to allow setting a TClass-property of a TCollectionItem at design time

I'm developing a component that works on several classes.
In order to allow adding the list of managed classes, I've written a TCollection's inherited class in which each item (inherited from TCollectionItem) defines a published "TargetClassName" property.
The "TargetClassName" property's setter function, calls the following function in order to find the corrisponding TClass:
function FindAnyClass(const Name: string): TClass;
var
ctx: TRttiContext;
typ: TRttiType;
list: TArray<TRttiType>;
begin
Result := nil;
ctx := TRttiContext.Create;
list := ctx.GetTypes;
for typ in list do
begin
if typ.IsInstance and (EndsText(Name, typ.Name)) then
begin
Result := typ.AsInstance.MetaClassType;
break;
end;
end;
ctx.Free;
end;
(Thanks to Dalija Prasnikar for writing the function Get class by its name in Delphi).
Now, I'm wondering if there's a better way to allow adding classes to a TCollectionItem at design time.. What do you think about it?
Hope to read interesting solutions!
Thanks to all.
in creation on TCollection You need To introduce Collation Class
it's Posible in two way
1 : hard coded in create time X := TMycollation.Create(TMyCollationClass)
2 : your solution X := TMycollation.Create(FindAnyClass('TMyCollationClass'));

How to copy data from one class into another class

how to copy data from one class into a second class using operator overloading with DELPHI ?
my dummy app goes like this :
type
TClass_A = class
a: String;
end;
TClass_B = class(TClass_A)
b: String;
end;
implementation
procedure TForm1.Button1Click(Sender: TObject);
var
a: TClass_A;
b: TClass_B;
begin
a := TClass_A.Create;
b := TClass_B.create;
b := a; // <<-- What code should be here? Can I overload := operator?
end;
The assignment operator cannot be overloaded in Delphi.
You will need to introduce a method to perform the copying. An example of how this might be done is TPersistent.Assign. It would be perfectly reasonable for you to derive from TPersistent and override the virtual Assign method to implement the desired functionality.
Here is a small example of how to do this with new style RTTI (D2010 and higher).
Please note that this is a very basic example which only copies fields (not properties) and works best with basic classes (do not use this with TComponent), the fields in the destination object must be the same type. FYI, there are better examples out there :)
uses
Rtti,...
function CopyObject(const FromObj, ToObj: TObject): Boolean;
var
Ctx : TRTTIContext;
FromObjType : TRttiType;
ToObjType : TRttiType;
FromField : TRttiField;
ToField : TRttiField;
begin
Result := False;
FromObjType := Ctx.GetType(FromObj.ClassInfo);
ToObjType := Ctx.GetType(ToObj.ClassInfo);
for FromField in FromObjType.GetFields do
begin
ToField := ToObjType.GetField(FromField.Name);
if Assigned(ToField) then
begin
if ToField.FieldType = FromField.FieldType then
ToField.SetValue(ToObj, FromField.GetValue(FromObj));
Result := True;
end;
end;
end;