Welcome to our new forum
All users of the legacy CODESYS Forums, please create a new account at account.codesys.com. But make sure to use the same E-Mail address as in the old Forum. Then your posts will be matched. Close

Advice on OOP Code Structure

2016-08-28
2019-04-10
1 2 > >> (Page 1 of 2)
  • Maximilian-K - 2016-08-28

    Hi all,

    I'm looking for some advice on the best way to structure my code with an OOP approach and in a way that minimizes global variable use, specifically mapping physical IO.

    My program is for a hydraulic CANbus control system that utilizes a variety of ifm I/O modules. Currently I have structs for each IO module type that I use to map all of the IO. I then pass that struct into a function block for each IO module to do basic processing and that contains some node diagnostics. (See pictures)

    My main problem is that the IO that goes to these modules can come from different POUs in the code, such as outputs for valves, outputs for lights, all the various IO, etc.Currently I have most of these setup as global variables, which I know is bad practice. However, since the IO is coming from all over the place (and there are hundreds of IO) I can not think of a clean way to pass all of these from other POUs in the code directly. The IO is quite varied, so a single module may have IO related to different parts of the program.

    My other question is regarding passing on outputs to these function blocks from objects. For example lets say I make a function block instance for each of the valves, and in the portion of the code that controls the logic, I could have a method that I call such as Valve1.open() for each one. Would the best approach be to make a property of the function block (i.e. Valve1.commandedOpen) that I then use as an input to the IO module function block? It seems like a lot of extra work as opposed to having one general variable openValve1 that I manipulate and also pass to the IO module FB as an input.

    Hopefully my questions are clear. I appreciate any feedback. If any one had any projects that do something similar that they can share that would help greatly.

    IMG: ModuleIOMapping.JPG

    IMG: IOModuleFB.JPG

     
  • learnetk - 2016-08-29

    Hi,

    This is nice topic to discuss, how structure a function block in line with OOP approch. Addition of the methods and properties have created a confusion of what should be defined as Input/Output and what should be made available properties.
    It would be really great to know how the experinced user do it.

    I hope there is some good document in this regard.

     
  • rickj - 2016-08-29

    Hi Max,

    There are probably as many opinions about this as there are stars in the sky, so I will just share mine.

    First of all I'd like to point out that it's not necessary to define all those global variables. You could just define the function block in global space and then directly access it's input and output parameters. For example instead of OUT_LoadSensorOveride you could use MODULE_7.Port2_Pin4_DigOut.

    On the surface that doesn't look very useful however if a different instance name were chosen, EngineRoom, for example then the signal name would be EngineRoom.Port2_Pin4_DigOut. That's a little better but still could be improved. The CR2032_IO module can be sub-classed using the EXTENDS keyword so that parameters would be named according to their function rather than addresses. So instead of having a parameter called Port2_Pin4_DigOut we would have oLoadSensorOveride. The lowercase 'o' is a convention I use (not the codesys recommended convention) to signify a hardware output. The resultant parameter name would then be EngineRoom.oLoadSensorOveride.

    I have noticed that you have both digital and pwm outputs for each pin. This would be a good application for properties. Properties are bound to functions, that are executed on read and write, rather than memory locations. If a pwm value is written them pwm functionality is enabled and if a digital value is written the pwm functionality is switched off.

    The other thing I notice is that your comments imply you are using a functional organizational model (all the valve outputs in one section, all the buttons somewhere else, all the lights in yet another section). You should consider using a relational organizational model where elements are grouped according to their relationships.

    Hope this helps

     
  • Anonymous - 2016-08-31

    Originally created by: scott_cunningham

    I'll chime in my 2 cents...

    I have been trying to push the OOP within my organization for the past two years. The main thing we learned, is we need to think differently. Often, we put too much into one function block, routine, object - whatever you want to call it. We have found, the more you break apart the "thing", the better off you are with definition, implementation and troubleshooting. Additionally, we often group objects together out of habit and complete wrong. Example: yes, an IO module is a thing, but each input is really it's own thing and may not have anything to do with the input right next to it, so don't call the IO module an object.

    In you example, I would break apart your IO module. Yes, it is a IO module, but look at the outputs and inputs - you are assigning them to a specific "thing": OilTemp, OilLevel, etc. Your bigger object (engine room) would consist of those smaller objects.

    Using REFERENCE TO and FB_INIT is nice because you can force a hardcoded link between your code and the hardware pin, without having to map your data every PLC scan.

    Properties are nice to use because you can run a bit of code, instead of having to call the FB itself (see screenshot). Just beware you can't link properties to visualizations and they are really function calls, not variables.

    Specifics:

    1. Keep your fieldbus mapping descriptive to reflect the hardware. Define a GVL that is called hardware and define your IO vars and structures so they speak physical locations (application.hardware.rack7.in1).

    2. Create function blocks for each small object (OilTemperature, OilLevel, etc). Their job in life is very basic - take the data from the fieldbus and make it mean something. Maybe you get fancy and start filtering and conditioning the signals with some hysteresis and time delays...(ok, maybe later).

    3. In each of those function blocks, define a local var that is a REFERENCE TO xxx (match your hardware var you need to link to).

    4. In each of those function blocks, define a custom FB_INIT method where you instantiate the FB with a link to the hardware variable. Have the FB_INIT set your local variable reference to the hardware variable. Now you have only one hardware variable, which your small object is linked to - no copying values every PLC scan is required.

    5. Take these small objects and have outputs (or properties) that are useful to your engine control.

    Something like this:

    IMG: oop.png

    IMG: oop2.png

     
  • Maximilian-K - 2016-09-15

    Guys,

    Thanks for taking the time to respond to this and give your two cents. Of course I know there are a plethora of ways to implement this and there isn't really a "best" way. Just hearing the different views on how to approach this helps a lot to refine what works best for my applications, and hopefully others reading this will get some ideas.

    Rick,
    Thanks for the feedback, it really helps. I see the benefit to your approach and I like the idea of directly accessing the IO parameters from a global function.

    I have used EXTENDS before, but not to change parameter names. So each new subclass would have meaningful variable names and they would be associated to the address using the SUPER variable? Or am I missing something?

    Can you expand further on using properties to change between pwm and digital output functionality? Are you suggesting having two properties, one for the PWM output and another for the digital output, and having code within each that disables the other functionality? I don't follow.

    Scott,
    You make really good points as well. And thanks for taking the time to create that example. I was not very familiar with using FB_INIT and REFERENCE TO, but your example makes it clear. I am always unsure of how small is too small when defining my function blocks. How far do you take this? For example, in your applications would you define a class for something as simple as a push button or a light? Part of me thinks that would just make things more complicated, but perhaps there is a good reason to do it.

    When you or someone in your organization is writing code do you have a process to define all the various classes/functions before beginning, such as UML or is it decided on the fly based on your experience?

    You mentioned that you cannot link properties to visualizations. How do you get around this? Do you have a similar OOP approach when designing the visualizations? I currently just have my visualizations reference my global variables, but am starting to think that it may be better to use the element interfaces more strictly, or even just create unique data structures for each screen on my visualization.

    Thanks,
    Max

     
  • Anonymous - 2016-09-15

    Originally created by: scott_cunningham

    Max - to most, using any OOP makes the application "complicated". In my experiences in industrial automation, almost all (or all) of my customers and my coworkers customers are not using OOP. In some cases, it is just a better idea to stay very basic and use "brute force" programming instead of 12 instances, blah, blah.... I keep in mind of an experience I had were I was looking at breaking up the system into many FBs and my customer sent me his code - one program with 48 rungs of ladder! We definitely went with his solution!

    On the other hand, I have used objects for buttons and lights - even with interfaces so I have a constant type. I have just used a variable many times for a button only to find out I needed to debounce the button or add a time filter for whatever reason - a much easier proposition when the button is a FB and not just a variable.

    Yes, properties cannot be accessed by the visualization, because a property is a get function behind the scenes and the visualization needs a variable. You also need a variable instead of a property for remoteIO and remote HMI displays. For a button, I solve by having the "raw button press" variable be a VAR_INPUT and the "button state" be a VAR_OUTPUT. Then other programs have access without a property. You also need to call the button FB every PLC scan to run your code (state update, timers, etc).

    I use this same idea for digital inputs and outputs. I can then easily add these to any other object - say a drive with 4 digital inputs and 4 digital outputs, or a remoteIO module with 16 outputs. With my digital output FB, I have properties such as: DelayTime, NPNLogicMode, On and methods such as TurnOn, TurnOff and Toggle. Now, in my code I can write either Output3.TurnOn() or Output3.On := xxxx. My EtherCAT mapping links to the VAR_OUTPUT variable. And if I need NPNLogic (FALSE = signal), I can simply set that property and not change any other code (not the case with a variable only!).

     
  • josepmariarams - 2016-09-15

    Hi.

    For me an dinput or outputs is an fb. If an fb representing an pneumàtic cillinder needs 2 di and 2 do, i declare all Inside fb pneumàtic cillinder.

    All the phisical i/o are packed Inside a fb named dio_ref. Normally, all fb has an var-inout of kind dio_ref, and are connected to the packed ios.

    I configure in every fb_digital input to which phisical dio of the packed dios are connected. The configuration of every dio is saved in disk. In this manner i can configure the connetions without touching code. And I can change the physical pin of an dio in execution time.

    Enviat des del meu Aquaris M5.5 usant Tapatalk

     
  • rickj - 2016-09-15

    Hi Max,

    Zitat:
    I have used EXTENDS before, but not to change parameter names. So each new subclass would have meaningful variable names and they would be associated to the address using the SUPER variable? Or am I missing something?

    I would make the base class without parameters, just internal variables with generic names. Then in the sub-class assign the generic variables to specific parameters.

       GenericIn := InParameter;
          ;
          ;      // some code 
          ;
       OutParameter := GenericOut;
    

    Zitat:
    Can you expand further on using properties to change between pwm and digital output functionality? Are you suggesting having two properties, one for the PWM output and another for the digital output, and having code within each that disables the other functionality? I don't follow.

    Yes, that's what I had in mind...use two different properties. The property could contain the PWM functionality so that if it's not being used then no code is executed or some other variation.

     
  • yannickasselin1 - 2016-09-16

    By the way, properties can be accessed by the visualization if you put this: {attribute 'monitoring' := 'call'} in the declaration of the property.
    So the visualization actually calls the get and set functions.
    I used it and it works. But I would like to find a way to call methods from the visualization. Does not seem to work for now.

     
  • Maximilian-K - 2016-09-19

    Scott,

    Zitat:
    Yes, properties cannot be accessed by the visualization, because a property is a get function behind the scenes and the visualization needs a variable. You also need a variable instead of a property for remoteIO and remote HMI displays. For a button, I solve by having the "raw button press" variable be a VAR_INPUT and the "button state" be a VAR_OUTPUT.

    Good idea. However, try out the solution yannickasselin1 posted. I've only done a little bit of testing with it, but it works out well so far.

    Zitat:
    I use this same idea for digital inputs and outputs. I can then easily add these to any other object - say a drive with 4 digital inputs and 4 digital outputs, or a remoteIO module with 16 outputs. With my digital output FB, I have properties such as: DelayTime, NPNLogicMode, On and methods such as TurnOn, TurnOff and Toggle. Now, in my code I can write either Output3.TurnOn() or Output3.On := xxxx. My EtherCAT mapping links to the VAR_OUTPUT variable. And if I need NPNLogic (FALSE = signal), I can simply set that property and not change any other code (not the case with a variable only!).

    I think a lot of what I need to work out the design pattern I use for the various parts of my program and the various abstractions I make for my classes. For example, whether to treat an I/O as the final device, and do the mapping to the physical IO module through references, as in your first example, or to do it as you describe above, where you have the mapping directly to an output of a function block instead using references.

    I have been testing your suggestions, and so far they work really well with my application. One issue I ran into was having nested classes that rely on FB_INIT and REFERENCE TO to link to the physical IO. Going back to your original example, if I wanted to make a larger class for the engine which within it I would instantiate the various sensor function blocks so then I could have multiple engine instances in my application.

    FUNCTION BLOCK clsEngine
    VAR
       OilTemp: clsOilTemperature(RefTemp := ??????)
    END_VAR
    

    I have not been able to come up with a way for the FB_INIT of clsEngine to set the reference for clsOilTemperature. Is there a way around this or would you just define the OilTemp class separate from the Engine class and pass data between the two?

     
  • Anonymous - 2016-09-20

    Originally created by: scott_cunningham

    I didn't follow your problem, but I have had situations where I've had to define the reference after the object exists. For these cases, instead of using the FB_INIT solution, I used a method to setup the reference. This can be called at anytime. To be safe, I create an internal dummy variable that my reference points to until the method is called to avoid program exceptions.

    FB EXAMPLE
    VAR
        SafeRef : BOOL;
        Link : REFERENCE TO BOOL := SafeRef;
    END_VAR
    METHOD MapLink
    VAR_INPUT
        Link : REFERENCE TO BOOL;
    END_VAR
    THIS^.Link REF= Link;
    

    Maybe this helps!

     
  • learnetk - 2016-09-22

    yannickasselin1 hat geschrieben:
    By the way, properties can be accessed by the visualization if you put this: {attribute 'monitoring' := 'call'} in the declaration of the property.
    So the visualization actually calls the get and set functions.
    I used it and it works. But I would like to find a way to call methods from the visualization. Does not seem to work for now.

    Hi Yannickasselin,
    Could you please make an example for this.

    Thanks.

     
  • Maximilian-K - 2016-09-22

    Learnetk,
    I'll chime in as I already have been playing with this in simple examples.
    See my pictures below. The bottom shows using the property to turn on a light (that gray blob). You just type the pragma in the declaration of the property. The only thing I've noticed, is that you cannot use properties with visu elements such as image switcher, as it is looking for a reference since it needs to read & write the variable.

    The top picture shows calling methods from the buttons on the visu.

    Hopefully this helps.

    IMG: FB

    IMG: FB

     
  • learnetk - 2016-09-22

    Hi Maximilian,

    Thanks that was really fast.
    Does this work only for property returning boolean values ? I tried use a property returning Int and tried using it from Visu with the 'Text Variable' property, but it gives me compile error 'is not allowed as operand fpr ADR'.

    Thanks again and warm regards

     
  • Maximilian-K - 2016-09-23

    Learnetk,

    Good catch, I didn't notice that limitation at first. I'm guessing it's because you can technically read & write that text value through the visu element.

    I played around and I think I have a solution, though there may be a better way. Create a visualization that has the element that you want to display the value and in the interface of the visualization define a VAR_INPUT that will be the value you want to display. Implement this visualization through a frame in your main visualization and you can set the property to the frame element. See my pictures below.

    However, I still haven't found a way to link a read/write visu element to a property, which is pretty important in my applications. If you come up with a way to do this or run into any further issues, let me know. I'll try to help as much as I can and they'll most likely be issues I run into at some point.

    IMG: Visu

    IMG: Visu

     
  • learnetk - 2016-09-23

    Hi Maximilian,
    cool, that works !! Thanks, but i notice that you do not need to add the Pragma
    {attribute 'monitoring':='call'} to achieve this(the boolean and integer values).
    It works without the pragma too, and the help suggests the this Pragma has a
    different use.

    Have a nice weekend.

     
  • Maximilian-K - 2016-09-24

    Huh, I guess we didn't need the pragma at all.

    Do you by chance know of a way to get properties to link to retain variables? I know I could set up the whole function block as retained, but that is an extremely inefficient way of doing it. Any suggestions would be appreciated.

    Thanks.

     
  • Anonymous - 2016-09-25

    Originally created by: scott_cunningham

    I believe the pragma was meant more for online debugging (you can see the property result directly like it was a var).

    The best solution I have found When needed retain variables inside a FB is to just define a variable in the retain space and have a REFERENCE TO variable defined in your FB. Use FB_INIT or a method to point to the retain. If using a method instead of FB_INIT, be sure to define a local dummy variable so your REFERENCE TO var can point to it. Otherwise if you forget to establish the link, you get an exception error (like trying to use an uninitialized pointer).

     
  • josepmariarams - 2016-09-25

    Hi Maximilian

    About previous discussions about passing refs in fb constructor ( Fb_init). If it is true that passing variables of kind ref via constructor makes code fastest it could make loose visibility about the machine structure.

    In this case colud be possible that motor has al action in cfc mode wher you can design the logical part of the machine (connections between inner classes).

    As normally, all fb has sequential code too. I made the fbs with an action with the sequential code ( in cfc or via switch case). And another action with the logical connections. Both actions are called by the body of motor.

    Enviat des del meu Aquaris M5.5 usant Tapatalk

     
  • Anonymous - 2016-10-23

    Originally created by: scott_cunningham

    Nice write up, but sometimes we need people to just understand they should create a FB (object) to decode status words instead of decoding status words in each program....

    Maybe a list of baby steps into OOP!!! Keep up the fight

     
  • Maximilian-K - 2016-10-26

    Rick, thanks for sharing! Great article.

    Scott, does that mean you are volunteering to write the OOP baby steps article?? And what do you mean by status words?

     
  • rickj - 2016-10-27

    Thanks guys, for the kind words. It's going to also be in the January print edition, perhaps I can get them to use the actual illustrations I submitted instead of the placeholders I included with the text.

    I am planning on writing another article as soon as I settle on the next topic. Some of the ideas I have so far are listed below but I would be interested yo hear any other ideas you may have.

      • Numerous methods exist but tend to be after-the-fact analysis rather than methods that can be used upfront to estimate complexity of prospective projects (i.e. during proposal and specification phase). I would propose to use a state machine based approach where system operations and the sequential steps required to carry them out are represented by states. The total number of states and transitions between states is a good indicator of complexity.
      • All devices should have a common interface and interact with their main program in the same manner. The details of how the device operates are encapsulated in the device driver object and a common interface exposed externally.
      • Basically a laundry list discussion of items such as auto-complete, syntax highlighting, mouse-over popups, compilers, library management, advanced data types (i.e. ENUM, POINTER, etc), full support of IEC-61131-3, etc. Discuss benefits of new OEM vs old Proprietary approach.
      • Present the reasons to have one (reduce likelyhood of programming errors). Present various techniques that can be applied (camel-case, lowercase, underscore as do surrogate...) and issues that should be considered (sorting order, general to specific, grouping).
     
  • yannickasselin1 - 2016-10-28

    Can't wait to read it!

     
1 2 > >> (Page 1 of 2)

Log in to post a comment.