General Python programming approach

Hello.

I have just started my first Ignition project and I am trying to find the best approach to create it well structured, easy to maintain and clean code. The main question is, procedural or objective?

Because my system will have different states (e.g. running, stopped, alarm etc.), many counters, flags etc. and it will make many actions on that fields, the natural choice seemed to be objective programming.

So, I have decided to create few different Python classes:

  • SystemTag, which holds current tag value and is able to read and set values
  • SystemCounter, which inherites from SystemTag, and has additional method for incrementing and decrementing values
  • System - main class, which contains fields of type SystemTag, SystemCounter and methods for changing system state based on that fields and additional logic

I have also created UDT. One instance of UDT will corresond to one object of System class. Each tag and counter inside UDT instance will corresponds to SystemTag or SystemCounter fields inside System object.

I have put that classes into shared resources and in the shared resources, I have created an instance of System class. That way, the object is accessible from Tag events and all other Gateway scoped scripting places.

To keep consistency of values between System object and UDT, I have put into all UDT Tags value change events, System object method to update field inside taht System class object e.g. system.tag1.updateValue().

Generally this works good, but I have some questions:

  1. Is that approach makes sense in opinion of more expierienced Ignition programmers? I have seen few projects of quite similar systems in Ignition, and they all have been procedural. Long functions, in many different places, in my opinion not easy to read and maintain.
  2. About client scope. If we leave it like that, all client sessions will create new instance of that System object. Correct? So I have decided to create that object conditionally, if we have Gateway scope, return full object, if we have Client scope, return a little bit simpler object (without system logic), just with methods to increment counters etc. This Client scope methods will work a little bit different, beacuse each time we call increment() method, we need to read tag, increment and write again (with Gateway scope reading is not necessary, because actual value is stored inside System object field). Makes sense?
  3. What is the best approach to detect current scope via scripting? Currently I have used workaround:
try:
   system.gui
   #create Client scope object
except:
   #create Gateway scope object

Can’t wait for you opinions. If I was not clear enough, don’t hasitate to ask for some pictures, diagrams or something :slight_smile:

Best regards,
maci3k

I use very few python classes in my Ignition deployments. In OOP, objects are generally used to hold structured state, but in Ignition, you really need to keep state in tags or databases, particularly for gateway scopes.

The classes I define in script modules for gateway scopes are convenience wrappers or caches for complex state, but the “source of truth” is the tags or queries that are used to instantiate those classes. The most important thing to keep in mind is that your gateway interpreter environments can be thrown away (restarted) without notice, due to project saves from the designer, and module installs/updates from the gateway web interface.

In Vision client scope (and the designer), the interpreter environment restarts are obvious–they happen when project updates are pushed, or a module update forces a complete restart. In the java Swing environment, python classes that extend java classes are necessary for advanced operations, particularly for fancy Power Table functionality. Otherwise, I use them to wrap or cache complex state from tag or DB origins, like I do in the gateway.

Now, I’m not knocking object oriented programming. Ignition is very much object oriented. But it is best to think about jython in Ignition as a tool to “override” base methods of Ignition’s native objects. Ignition is very much an event-driven platform, and event scripts are Ignition’s way to allow your code to participate in the over-arching object environment. Naturally, jython event handlers look procedural, because, like native methods, they are expected to do their job (including manipulating the state they are attached to) and exit.

That said, there are cases where Ignition doesn’t provide functionality you need, and jython does. When you need to execute such code, managing its own state, you generally need a long-lived thread and/or long-lived jython objects. These are available, via system.util.invokeAsynchronous() and system.util.getGlobals(), but you must take great care to deliberately destroy/replace such items when the underlying codes is updated, or you will leak huge blocks of memory. (And you may need to use my LifeCycle module instead of getGlobals, depending on your Ignition version.)

@pturmel thank you for your interesting reply. In fact, you have not said my approach is bad, which is good for me :slight_smile:

but in Ignition, you really need to keep state in tags or databases, particularly for gateway scopes.

In the approach I have proposed, you still have this. The only difference is that you have clear representation of tags (or database fields) within classes and that you read changes of that values realtime (using Value change tag event), so that when you access tag value via Class instance, you do not need to read tag value, because you can be sure you have actual value on Instance field (I mean in gateway scope).

The most important thing to keep in mind is that your gateway interpreter environments can be thrown away (restarted) without notice, due to project saves from the designer, and module installs/updates from the gateway web interface.

Yes sure. That's why implementing class you have to take care, that all fields are initialized with current tag's values when recreating object.

In my opinion, the time cost of OOP when starting from scratch is a little bit higher. But we get much more advantages (even when object is not a "source of trouth" but only a copy, let's say). We can use hermetization and inheritance to make code more readable and easy to use by others. We can easly divide project into pieces working in a team. The OO code is much easier for future development (even by different programmers).
For example, when you take a look on the UDT instance in my project, you can be sure, that the logic behind tags is inside the class with the same name as UDT datatype, and there is no need to search through different functions. This is huge advantage, especially working in a team.