Increasing the tag value in different rates

I am trying to start a tag value from 0 and increase with different rate at different conditions. Example, at High rate, the value starts to increase from 0 to a value, then when rate condition changed to Medium, it continuous to increase the value but with a medium rate, and so on until it resets to 0 and start again.
Below is my script, but it doesn’t work:
(I am not very experience in this so it might look simple for many but has been challenging for me, please advise):

Define the tag paths

   X_tag_path = ["[default]PredictionTabTags/ModelSuggestedO2Rate.value"]  
   Test_tag_path = ["[default]PredictionTabTags/O2LanceTEST.value"]  
   
   # Define the increase rates for eachX_tag value
   increase_rate = {
       "High": 60 / self.props.data[0].High_O2_Flow,   # Increase by 1 every 3 seconds to reach 1800 in a minute
       "Medium": 60 / self.props.data[0].Medium_O2_Flow, # Increase by 1 every 2.5 seconds to reach 1200 in a minute
       "Low": 60 / self.props.data[0].Low_O2_Flow     # Increase by 1 every 1.67 seconds to reach 900 in a minute
   }
   
   # Function to update the Test tag value based on the value of X_tag
   def updateTestValue(tagPath, previousValue, currentValue, initialChange, missedEvents):
       global increase_rate
       if currentValue.value in increase_rate:
           system.tag.writeBlocking(Test_tag_path, 0)  # Reset Test tag to 0
           rate = increase_rate[currentValue.value]
           system.tag.writeBlocking(Test_tag_path, 0, 0, rate)  # Start increasing Test tag with the calculated rate
       else:
           system.tag.writeBlocking(Test_tag_path, 0)  # Reset Test tag to 0 if X_tag is not one of the defined values
   
   # Bind the updateTestValue function to the X_tag
   system.tag.addTagChangeListener(X_tag_path, updateTestValue)

You can't just make up system functions. And this wouldn't be appropriate for a tag change event anyways. (And tag listeners run when something else changes a tag--they do not produce changes in the tag.)

This needs a state machine, which typically means a timer event.

Make a project library script with your critical code in a function, and your constant definitions outside any function. The function would use system.tag.readBlocking() to retrieve the current value, compute the new value, then use system.tag.writeBlocking() to update the tag.

In a gateway timer event, put a one-line call to your library function, like so:

myLibraryScript.myGeneratorFunction()

Set the pace of the timer event to something appropriate for your purpose, and corresponding to your increase/decrease constants.

And stop trusting ChatGPT (or whatever you're using) to understand anything about Ignition. It can maybe write independent Python code for you. It cannot successfully write arbitrary scripts that will drop into Ignition.

7 Likes

Thanks for the advise @PGriffith , as mentioned, I am not experienced in Ignition and have a deadline, so I need to get help wherever possible. Hopefully, I'll have time after this project completion to learn more and not relay on other sources.

Wading through the drek ChatGPT (or other LLM) produces is very demotivating for volunteers like me. Just don't.

Thanks @pturmel, I kind of understand now, but not fully.
I created a timer in the gateway as you explained, got rid of the listener, and used system.tag.readBlocking() to get the current value. and then write the new value, but still no luck. I'm sure I am not doing it right, yet to figure it out.

Post your code (as text, in this forum's "preformatted text" block). Use screenshots only to add context surrounding the code (timer settings, script library arrangement, perhaps).

Then we might spot what your problem is.

I should explain what I need to do first:
I created a tag called "ModelSuggestedO2Rate", this tag will read the rate values of Low, Medium or High.
Another tag called "O2LanceTEST", this is the tag I want to increase the value from 0 at a rate. It usually start with High (1800/min), up to a value let's say 30000 (This value could change as it is controlled by other factors, this is just an example of the value) , the rate tag "ModelSuggestedO2Rate" changes to medium, with rate of 1200/min. so now I want the value of the "O2LanceTEST" tag to continue increasing from 30000 but at the medium rate (1200/min), then at "O2LanceTEST" tag value of Low, the value increases at Low rate (900/min).
The value changes will be written into the "O2LanceTEST" tag, it will be reset to 0 when the "ModelSuggestedO2Rate" tag is not Low, Medium or High.

Thanks

Ok, that's done.

Now you can post your code. :man_shrugging:

# Define the rates of increase for Y tag
	rates = {'Low': 900, 'Medium': 1200, 'High': 1800}
	
	# Initialize the previous value of X tag to None
	previous_x = None
	
	# Initialize the value of Y tag
	y_value = 0
		# Function to update Y tag based on X tag value
	def update_y(x_tag_value, y_tag_value):
	    global previous_x, y_value
	    # Check if X tag value has changed
	    if x_tag_value != previous_x:
	        # Update the previous X tag value
	        previous_x = x_tag_value
	        # Check if the new X tag value is one of the specified rates
	        if x_tag_value in rates:
	            # Calculate the new Y tag value based on the rate for the current X tag value
	            y_value += rates[x_tag_value] / 60 # Divide by 60 to get the rate per second
	            # Write the new Y tag value to the Y tag
	            system.tag.writeBlocking("[default]PredictionTabTags/O2LanceTEST", y_value)
	        else:
	            # If X tag value is not one of the specified values, stop updating Y tag
	            return
	
	# Main loop to continuously check the X tag and update the Y tag
	while True:
	    # Read the current value of the X tag
	    x_tag_value = system.tag.readBlocking("[default]PredictionTabTags/ModelSuggestedO2Rate")
	    # Call the function to update the Y tag
	    update_y(x_tag_value, y_value)
	    # Sleep for 1 second before checking the tags again
	    time.sleep(1)

So much is wrong here.

First off, you've got an infinite while loop. Don't ever do this. Call this script with a periodic gateway script at a 1 second rate if you want it to execute continuously, but don't put an infinite loop.

Secondly, your readBlocking is reading a qualified value, so your call to update_y needs to be:
update_y(x_tag_value[0].value, y_value)

As long as your x_tag_value is a string of 'Low', 'Medium', or 'High', then you should be fine, otherwise, what value does x_tag_value return?

Edit: If you're trying to add every second a fraction of one of your rate values to the y value, this script won't work anyway. Try something more along these lines in a gateway timer script set for 1 second (modified from your script):

# Define the rates of increase for Y tag
rates = {'Low': 900, 'Medium': 1200, 'High': 1800}
tags = system.tag.readBlocking(["[default]PredictionTabTags/ModelSuggestedO2Rate", "[default]PredictionTabTags/O2LanceTEST"])

x_value = tags[0].value
y_value = tags[1].value

# Check if the new X tag value is one of the specified rates
if x_value in rates:
	# Calculate the new Y tag value based on the rate for the current X tag value
	y_value += rates[x_value] / 60 # Divide by 60 to get the rate per second
	# Write the new Y tag value to the Y tag
	system.tag.writeBlocking(["[default]PredictionTabTags/O2LanceTEST"], [y_value])
3 Likes
    else:
        # If X tag value is not one of the specified values, stop updating Y tag
        return

These lines aren't required.

Thank you so much @michael.flagler it works very well.

Did you eliminate the while loop and run the modified script from a gateway timer event?

I have another advice for you:
Don't over comment your code.

Examples of comments no one will ever need:

# Initialize the previous value of X tag to None
previous_x = None

If someone doesn't understand that code, no comment will ever help.

# Check if X tag value has changed
if x_tag_value != previous_x:

Pretty much as above. The variables names are clear, you don't need to over-explain.

# Update the previous X tag value
previous_x = x_tag_value

I guess you get the point by now

# Check if the new X tag value is one of the specified rates
if x_tag_value in rates:

The code is clear enough here as well.

# Calculate the new Y tag value based on the rate for the current X tag value
y_value += rates[x_tag_value] / 60 # Divide by 60 to get the rate per second

That line could warrant a comment, but I suggest explaining why you're doing something instead of what you're doing. Anyone can see from the code that it's doing a calculation, and adding a comment to tell you what variables you're using in that calculation doesn't say much. # calculate the rate of {whatever} per second is more helpful. But using a self-explaining variable name would render the whole comment useless anyway.

# Write the new Y tag value to the Y tag
system.tag.writeBlocking("[default]PredictionTabTags/O2LanceTEST", y_value)

# Read the current value of the X tag
x_tag_value = system.tag.readBlocking("[default]PredictionTabTags/ModelSuggestedO2Rate")

Yes, the function names contains tag.write* and tag.read*. The reader should be able to figure out that they write to a tag / read a tag.

# Call the function to update the Y tag
update_y(x_tag_value, y_value)

Here as well, the function is called update_y. You don't need a comment that says "call the function to update y"

# Sleep for 1 second before checking the tags again
time.sleep(1)

You probably already know by now what I would have said, so I won't.

The point is: comments should HELP the reader understand what's going on. If they don't, remove them.
Too many comments just add bloat and make code hard to read.

Over commenting is a pet peeve of mine. I tend to believe that if you need to comment everything, it should be better written in the first place as to not require comments.
I use 2 types of comments:

  • the docstring that explains what a function is for and how to use it: what parameters it expects, what it returns, maybe give an example if it's not straight forward.
    While I like functions that don't need too much explanation (clear name, obvious parameter names, etc), having a consistent doc string format helps create a sense of cohesion in a code base, and more importantly they allow auto-generated documentation and can be retrieved through introspection (with help())
  • inline comments that explain the logic or a difficult line of code (regex for example).
    You're using an equation ? Explain that.
    You're using something that's not obvious ? Explain that !

Here are examples from my own code:

# NFKD: https://fr.wikipedia.org/wiki/Normalisation_Unicode#NFKD
nfkd_form = normalize('NFKD', s)

Do you know what 'NKFD' is ? Me neither. When I wrote this, I knew. Now ? Couldn't remember for the life of me.

def chunks(it, size, fval=None):
	"""
	'Chunkify' an iterable.
	Splits 'it' into chunks of size 'size', padding with 'fval' if necessary.
	
	params:
		it (iterable):	the source iterable to split into chunks
		size (int):		the size of the chunks
		fval ():		a value to use for padding if the last chunk's size is less than 'size'
	return:
		an iterator over a list of lists
	
	examples:
	iterable = [1, 2, 3, 4, 5, 6, 7]
	size = 3
	fval = 'x'
	=> [[1, 2, 3], [4, 5, 6], [7, 'x', 'x']]
	
	with size = 4:
	=> [[1, 2, 3, 4], [5, 6, 7, 'x']]
	"""
	return zipl(*[iter(it)]*size, fillvalue=fval)

Explains what the function does, not how it does it. Who cares how it does it ! Frankly I just took the recipe from the itertools doc page.
But what it does and what to expect from it, that's what you need to know.
note: I took this example because the docstring is ridiculously long compared to the actual code, and because even if the actual code is way too hard to understand at first glance (at least for me), I still only explained how to use it, and not how it works.

Now I understand that it's my own VERY personal take on comments, but please, please, don't comment every little thing !

3 Likes

Understood, thank you for the explanations.

Smells like GPT output to me, honestly. Unless you specifically tell them to avoid being "helpful", most GPT systems seem to default to these totally unnecessary comments.

2 Likes

Solid advice, and this is an area that I probably need to work on improving. I've been super guilty of writing more comments than the minimum necessary to understand the code, and I imagine it mostly happens when I put my commenting off until the end of the day when my brain is no longer firing on all cylinders.

1 Like

I rarely comment code when I'm writing it, but rather before or after.
Before is a good time to write a basic docstring for the functions you're about to write - you already know what it will do, and if you don't it can help you figure it out (though you probably shouldn't be writing a function that you don't know the purpose of)
And when you're done with a piece of code, read it, and comment the things that made you stop or took time to read. Those are likely the things that will need some explanation.

There's another type of comment I didn't mention and that is helpful when working on the same codebase with other people:

# modified on 27/04/2024 by pascal:
# changed x to y to avoid z issue

Those are not meant to stay forever, but they're super helpful during dev, especially in an environment like ignition where running diffs is unpractical.
Actually I even do it (kind of) when working alone on something that I know I will have to get back to. I leave breadcrumbs to easily find what I should be working on.

3 Likes

This makes sense. While I'm developing the code, it feels like a waste of time to comment because what I'm typing is just as likely to get deleted or rewritten as it is to be kept. I start by getting the code to do what I want, and then spend time making it correct: meshing repetitive stuff into concise functions, simplifying convolution, eliminating stuff that was written but wasn't needed, etc. [Not to mention the approaches that were tried, didn't work, and were deleted completely] It makes sense to me to wait until after everything has been hammered out into its final shape to start trying to explain it.

Here's the thing though; in the end, if I take the time to over comment, it works out better for my final product because I see things that I would have otherwise missed. It's kind of like an after the fact rubber duck programming where I'm spelling out what I did and why I did it, and a lot of times, I realize mistakes, misconceptions, or I simply have a better idea as a result of the effort. I probably don't need to fix this stage of my development process per say; I probably just need to be more diligent about cleaning it up afterwards.