Basic OOP concept "instances" resp. "references"

schaepper
2017-07-26
2017-07-27
  • schaepper - 2017-07-26

    Hello together

    We are willing to go for the "OOP" approach with our new PLC implementation. We are struggling a little bit with Codesys and how its supposed to be done in IEC 61131-3. At the moment we are writing down the basic architecture. Both of us have enough expierence with JAVA and C#.

    What we want to do is a simple Alarmhandling interface with basic OOP concepts. What's struggling us at the moment is the fact that the FB's seem to get copied when assigned to member variables resp. on method calls.

    So from our understanding.

    If I do this:

    var:
    memberVariableMotor1 : Motor;
    alarmhandler.RegisterAlarmSource(alarmSource := memberVariableMotor1);
    end_var:
    

    and then do this:

    memberVariableMotor1.ChangeInnerState();
    

    The alarmhandler would not see this change, because he got a copy. This led us to use ```

    REFERENCE TO

    in the declaration. But then we face either strange access violations during runtime because we seem to do something wrong or if we use the

    REF=

    ``` assignment, we got strange messages like:
    "Can not convert type Motor to type "Pointer To AlarmSourceInterface", even if the Motor does in fact implement the AlarmSourceInterface."

    Normally in any other language I would not ask such silly questions, but for "IEC-61131-3" it seems to be near to impossible to find usefull code or samples in the internet. I found some, but only a little, especially to the OOP implementations. And I also didn't find a lot of usefull books written after 1980 .

    Do you guys do it the same way with references or is there something we don't get?

    Really appreciate your Input

    BasicOOPAlarmInterface.project [115.63 KiB]

     
  • schaepper - 2017-07-26

    Thanks for your post, but I think i dont really understand what you mean.

    If have this Motor:

    //Start Var Declaration
    FUNCTION_BLOCK PUBLIC Motor IMPLEMENTS AlarmSourceInterface
    VAR_INPUT
       alarmSource: AlarmSourceInterface;
       alarmHandler: REFERENCE TO AlarmHandlerInterface;
    END_VAR
    VAR_OUTPUT
    END_VAR
    VAR
       mAlarmHandler: REFERENCE TO AlarmHandlerInterface;   
    END_VAR
    //End Var Declaration
    //Codeblock start
    this^.mAlarmHandler := alarmHandler; //I want to do this in FB_INIT but that will follow later
    //Codeblock End
    

    Now if I want to use the Motor I would do this in my PLC_PRG:

    //Start Var Declaration
    PROGRAM PLC_PRG
    VAR
        alarmHandler1:  AlarmHandler;
       motor1:   Motor;
    END_VAR
    //End Var Declaration
    //Codeblock start
    motor1(alarmHandler := alarmHandler1);
    motor1.RaiseAlarm();
    //Codeblock End
    

    When I now go for step by step debugging, here:
    motor1(alarmHandler := alarmHandler1); it looses the reference when I later on look on the variable inside the motor:

    I cant do this, because its not allowed by syntax:
    motor1(alarmHandler REF= alarmHandler1);

    So what do I need to do now? If I want to pass alarmHandler1 into the motor? What you think is an elegant way? I ask you this because then I could do this:
    mAlarmHandler.registerAlarm(THIS^); --> No errormessage
    mAlarmHandler.registerAlarmByReference(THIS^); --> Gives an errormessage as you explained.

    And I would not have the problem that I can't compile the code because it sais the errormessage I mentioned before.

    @Edit:
    PS thanks for the links, I think I already knew them...

     
  • Joan M - 2017-07-26

    THIS is a pointer not a reference...

    Probably the best in your case would be using something like:

    FUNCTION_BLOCK PUBLIC Motor IMPLEMENTS AlarmSourceInterface
    VAR_INPUT
       alarmSource: AlarmSourceInterface;
       alarmHandler: POINTER TO AlarmHandlerInterface;
    END_VAR
    VAR_OUTPUT
    END_VAR
    VAR
       mAlarmHandler: POINTER TO AlarmHandlerInterface;   
    END_VAR
    //End Var Declaration
    //Codeblock start
    this^.mAlarmHandler := alarmHandler; //I want to do this in FB_INIT but that will follow later
    //Codeblock End
    
    //Start Var Declaration
    PROGRAM PLC_PRG
    VAR
        alarmHandler1:  AlarmHandler;
       motor1:   Motor;
    END_VAR
    //End Var Declaration
    //Codeblock start
    motor1(alarmHandler := ADR(alarmHandler1));
    motor1.RaiseAlarm();
    //Codeblock End
    

    But in your case the most "elegant" way of doing it would be using a property and a reference or a pointer.
    If you want to use FB_Init I've used the pointer approach successfully so you should not have any problem doing that.

    Keep in mind that when you do an online change pointers can need to be reassigned as the memory addresses can move.

    Hope this helps.

     
  • josepmariarams - 2017-07-26

    Hi.

    Var_input variables are public fb_variables.

    Var_output are public with readonly access.

    Every cycle varinput and output are copied. If you pass an fb, the fb is full copied.

    You make two copies. One in varinput assignement and other in private variable.

    If you use varinout, only is copied the fb pointer, and you are working with the fb passed.

    Fb_init is the constructor, but it not accepts varinout, in that case you have to pass the pointetr and copy it to an internal variable.

    Pass in fb_init is faster because it is not necessary to copy the reference every cycle, but when your make the logical code could loose how the connectioms are made

     
  • schaepper - 2017-07-26

    Thanks for your answers, I did what you explained to me (change from "REFERENCE TO" to "POINTER TO"), and the i provide the instance by

    motor1(alarmHandler := ADR(alarmHandler1));
    

    But now it says "Cannot convert type 'Pointer to AlarmHandler' to type 'Pointer to AlarmhandlerInterface'. But AlarmHandler implements AlarmhandlerInterface.

    Variable definition from Motor looks like this:

    Zitat:
    FUNCTION_BLOCK PUBLIC Motor IMPLEMENTS AlarmSourceInterface
    VAR_INPUT
    alarmSource: AlarmSourceInterface;
    alarmHandler: POINTER TO AlarmHandlerInterface;
    END_VAR
    VAR_OUTPUT
    END_VAR
    VAR
    mAlarmHandler: POINTER TO AlarmHandlerInterface;
    END_VAR

    AlarmHanlderCode Looks like this:

    Zitat:
    FUNCTION_BLOCK PUBLIC Motor IMPLEMENTS AlarmSourceInterface
    VAR_INPUT
    alarmSource: AlarmSourceInterface;
    alarmHandler: POINTER TO AlarmHandlerInterface;
    END_VAR
    VAR_OUTPUT
    END_VAR
    VAR
    mAlarmHandler: POINTER TO AlarmHandlerInterface;
    END_VAR

    Additionaly since I changed mAlarmHandler to be a Pointer, it sais C0062 "mAlarmHandler" is no structured variable on the following piece of code:

    mAlarmHandler.registerAlarm(THIS^);
    

    And if I hover over the mAlarmHandler its sais something like "Program name, function or function block instance expected instead of 'mAlarmHandler.registerAlarm'". Is that the same reason, so I cant do this on a pointer?

    By the way:
    I want to avoid VAR_IN-OUT in any case if possible because it breaks my interfaces and object resp. FB's encapsulation "by definition". To me they would be like public variables and thats (at least to me) not a solution.

    Appreciate your help!

     
  • Joan M - 2017-07-26

    Take a look at the properties to get a more similar way to share variables with the outside world.

    Remember that the pointers doesn't allow typecasting here, if you define a pointer to a specific type you can't pass a different pointer there.

    The ^ sign means dereference here, so if you are passing this^ you are passing a copy of the complete function block (or the object this points to), but if you are passing this (without the ^) then you are passing the address (the pointer) which is what you want now.

    Just please remember to initialize the pointer address to 0 in the declaration and check if the pointer is different than 0 before using it to endure you won't have undesired page faults.

    Hope this helps.

     
  • schaepper - 2017-07-26

    I tried it without the ^ but it still gives me the same alarm.

    "Remember that the pointers doesn't allow typecasting here, if you define a pointer to a specific type you can't pass a different pointer there."

    What would be the solution? Just as an info: My goal is not to use references, or pointers, whatever I just want to pass the same instance of a FB around. If I do it the "classic" way without pointers,refs or anything then the FB's always get copied on asignments. I just want to overcome the copies.

    Basically I just want to use it like every other "OOP" language in the field like C#,JAVA,Python,C++ what ever...

    I attached my latest project to this post if someone is interested.

    BasicOOP.project [114.17 KiB]

     
  • Joan M - 2017-07-26

    pseudocode:

    function block motor
    VAR_INPUT
      ptrAlarmHandler : pointer to AH := 0;
    END_VAR
    --------------------------------------------------
    ptrAlarmHandler^.i := ptrAlarmHandler^.i + 1;
    
    function block AH
    VAR
      i : int := 0;
    END_VAR
    -------------------------
    
    PROGRAM MAIN
    VAR
      fbAlarmHandler : AH;
      fbMotor(ptrAlarmHandler := ADR(fbAlarmHandler));
    END_VAR
    -------------------------
    motor();  // After executing that code you should see i increasing by one.
    

    Hope this helps.

     
  • schaepper - 2017-07-26

    Joan M hat geschrieben:
    pseudocode:
    [code]PROGRAM MAIN
    VAR
    fbAlarmHandler : AH;
    fbMotor(ptrAlarmHandler := ADR(fbAlarmHandler));
    END_VAR


    motor();

    This is exactly the code that will not work because it sais:
    Canot convert type "Pointer To AlarmHandler" to type "POINTER to AlarmHandlerInterface"

    This where all the post is about...

     
  • Joan M - 2017-07-26

    Yes, the problem is the type.

    You can't typecast pointers.

    In my code the pointer points to the function block type not to it's interface.

    In your code, given the name it looks like it points to the interface the function block implements.

     
  • Joan M - 2017-07-26

    Check the last link I pasted in the first message, Stefan shows how to use interfaces correctly to get the most similar to what JAVA, C++, C# gives you.

    In any case it is not important what interface you implement, you are passing the function block pointer (in my example pseudo code).

    If you want to be able to use a more dynamic approach (given your background and the advantages it offers it is normal you would like to) then look at the Stefan link where good samples are implemented and deeply explained.

    Hope this helps.

     
  • schaepper - 2017-07-26

    Joan M hat geschrieben:
    Check the last link I pasted in the first message, Stefan shows how to use interfaces correctly to get the most similar to what JAVA, C++, C# gives you.
    In any case it is not important what interface you implement, you are passing the function block pointer (in my example pseudo code).
    If you want to be able to use a more dynamic approach (given your background and the advantages it offers it is normal you would like to) then look at the Stefan link where good samples are implemented and deeply explained.
    Hope this helps.

    You refering to this? https://stefanhenneken.wordpress.com/20 ... nterfaces/
    I read it completely, even time before but it does not (and in any article) explain the problem of "objects" always beeing copied if you don't use Reference To or similar approaches.

    If you don't use REFERENCE TO, POINTER TO or what ever if you just declare FB's as normal. And you pass a FB to another FB, and the "foreign" FB manipuliates the FB you are providing, he will manipulate the copy, not the FB you are providing.

    Here is my Pseudocode:
    motor1.ManipulateCopyTestString('ManipulationFromOutside');
    motor1(alarmHandler := alarmHandler1);
    POU1(motorInput := motor1);

    //This is the Methodcall from POU1
    motorInput.ManipulateCopyTestString('manipulatedinsideforeignfb');

    if you now look into "motor1" which was actually manipulated inside POU1, it will still have the string in it 'ManipulationFromOutside' which means POU1 manipulated a copy. And this is my Problem:

    Copy's don't make sense at all in an OOP aproach, and if I use pointers or references, I face the problems already describe in this post?

     
  • Joan M - 2017-07-26

    Check this one: https://stefanhenneken.wordpress.com/2014/11/16/iec-61131-6-abstract-factory-english/

    This is a great post on how to create dynamic function blocks using interfaces with proper samples and not only pseudocodes.

    Of course I understand you, copies are not what you are after.

    In my sample I'm using pointers everywhere to update from outside data that exists on the pointed function block. Unless I've made a mistake (more than possible given I've written the code directly here) it should work properly.

    See that article from Stefan which I guess it will give you some extra light on how to do it.

    Hope this helps.

     
  • josepmariarams - 2017-07-26

    Hi.

    Convert first your fb to an interface:

    A:Interface;
    B:fb_ whichimplements interface

    B=A

    And pass b sa var_input.

    Codesys treates as pointers. If you see in debug B you can see the address of A littlel bit offseted.

    But if an fb waits for an interface as input, before you have to cast it.

     
  • Anonymous - 2017-07-27

    Originally created by: Viacheslav Mezentsev

    schaepper hat geschrieben:
    I attached my latest project to this post if someone is interested.

    Example works for me (Codesys 3.5.11.0). Maybe I'm don't understand something?

    BasicOOP.project [114.17 KiB]

     
  • schaepper - 2017-07-27

    Hello everybody

    Thanks for your help, I finally found it yesterday. Interfaces are treated as pointer anyway so the declaration as POINTER TO or REFERENCE TO makes no sense resp. is not useful.
    So if you want to use a FB as "fb instance", define in your input/output/method/property "fbinstance : POINTER TO FBType" .
    Then you can pass the real pointer from the caller of an FB with ADR(fbinstance).The "opposite direction" would be the ^, so if you need to have the instance, instead of the pointer, often used if you refer to the instance with THIS, you will need the ^ so it doesnt take the pointer, it takes the instance.

    To hom it may be interessting:
    The goal of our ErrorHanlder code is:
    1. Encapsulate functionality to where it belongs to and have a "nice" architecture.
    2. Maybe more important, we will have libraries in our company. So if you want to be able to still encapsulate the functionaly of errorhandling inside the error causing element/FB you need kind of an Interface to the element which effectifly handles the error. If your component is in a library, there is no filesystem where you can log to, therre is no HMI you can show your error, resp. you don't know where you will be running.

    In our case we startet with a motor. So if the motor runs, and there is something wrong it needs to raise an error and keep the error state. As soon as the error is gone, it can start again. (Could be a circuitbreaker or a VFD in overload, what ever). We in our company normaly want the user to commit an error, so the motor stays in errorstate as long as the user does not commit the error.

    For this we have 2 Interfaces:
    - The AlarmHandlerInterface (this will be implemented by the FB which is actually resetting the error, making the decision if it has to be logged or viewed on a screen. And it handles user actions to reset the errors.
    - The AlarmSourceInterface. This is needed because we want like a callback method to the error causing element so we can reset its errorstate.

    INTERFACE AlarmHandlerInterface
    (* ------------------------------------ *)
    METHOD RegisterAlarm
    VAR_INPUT
      deviceWithAlarm:   AlarmSourceInterface;
    END_VAR
    
    INTERFACE AlarmSourceInterface
    (* ------------------------------------ *)
    METHOD ResetAlarm
    

    If the Motor which implements AlarmSourceInterface needs to raise an alarm, it needs to call the RegisterAlarm method from the AlarmHandler which implements the AlarmHandlerInterface (and needs a type of AlarmSourceInterface in this signature). We in our case provided the AlarmHandler to the Motor in the FB_Init method (like a contstructor) so its only called once.

    The AlarmHanlder will then put the Motor into an internal list, like this:

    METHOD RegisterAlarm
    VAR_INPUT
       deviceWithAlarm   :   AlarmSourceInterface;
    END_VAR
    (* ------------------------------------ *)
    devicesAlarmList[devicesInList]:= deviceWithAlarm;
    devicesInList:= devicesInList + 1;
    

    In this case its a fixed Array with 100 elements, im sure its possible to improve this to a dynamic approach. Because normally you don't know how many items will be added and need to increase the size of the array / list dynamically.

    This code is called when for example the operator on a HMI pressed a button to commit all errors:

    METHOD PUBLIC ResetAllAlarms
    (* ------------------------------------ *)
    //Resets all the alarms on each motor
    FOR currentDevice:= 0 TO devicesInList - 1 DO
      devicesAlarmList[currentDevice].resetAlarm();
    END_FOR
    devicesInList:= 0;
    

    It goes through the array which has only items of type AlarmSourceInterface in it and calls on each of this devices the reset alarm method.

    In the future we could also implement warnings and maybe sort of an autocommit depending if you use it.
    In our case, the Interfaces and the Motor itself will be in the library. The AlarmHandler is in the project were your work is done.

    I will add the project for you so you can have a look into it. I also added pragmas so you see were you have to so something.

    Greetings and thanks a lot for your patience. I hope I can put more samples here in the future.

    BasicOOP_AlarmHandling.project [113.41 KiB]

     

Log in to post a comment.