# Looping a script while tag is on

**URL:** <https://forum.inductiveautomation.com/t/looping-a-script-while-tag-is-on/93402>\
**Category:** Ignition\
**Created:** [July 18, 2024, 3:06pm UTC](https://forum.inductiveautomation.com/t/looping-a-script-while-tag-is-on/93402 "2024-07-18T15:06:37Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![jason\_melcher](https://avatars.discourse-cdn.com/v4/letter/j/6bbea6/32.png) [@jason\_melcher](https://forum.inductiveautomation.com/u/jason_melcher)\
**Post date:** [July 18, 2024, 3:06pm UTC](https://forum.inductiveautomation.com/t/looping-a-script-while-tag-is-on/93402/1 "2024-07-18T15:06:37Z")

</div>

Hi,  
fairly new to python coding but have experience elsewhere. I'm trying to make a simple script that will loop an audio file while a certain tag is on. In other programming languages I would just use a jump and a lable to continue the loop and use if / else statements but I havent figured out jumps and labels in python yet

I have a custom binding on the page directed at the tag I am looking to use

def transform(self, value, quality, timestamp):  
while value == True:  
self.getChild("Audio20").props.play = True  
import time  
time.sleep(30)  
else:  
self.getChild("Audio20").props.play = False

```
return value

```

The problem I am having is that when the tag turns on the sound plays, waits and plays again. but when the tag shuts off the sound continues to play every 30 seconds and it never shuts off.

I also tried to just use the self.props.loop on the audio file but I cant figure out how to put a delay in that. that would work too. If someone could explain that I might learn from that either way,

Also,  
can I make a variable for the entire page so if i use this script 30 times for different sounds I can change the wait time for all of them at the same time?

say x=30 and change the 30 in time.sleep(30) to time.sleep(x)? Where would I create that variable at?

---

<div class="post-metadata">

**Author:** ![Transistor](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/transistor/32/3380_2.png) [@Transistor](https://forum.inductiveautomation.com/u/Transistor)\
**Post date:** [July 18, 2024, 3:31pm UTC](https://forum.inductiveautomation.com/t/looping-a-script-while-tag-is-on/93402/2 "2024-07-18T15:31:32Z")

</div>

Welcome to the forum, Jason.

Tips:  
Have a look at [Wiki - how to post code on this forum](https://forum.inductiveautomation.com/t/wiki-how-to-post-code-on-this-forum/77831). It will then render as (but I'm guessing your indentation):

```python
def transform(self, value, quality, timestamp):
    while value == True:
        self.getChild("Audio20").props.play = True
        import time
        time.sleep(30)
    else:
        self.getChild("Audio20").props.play = False
    return value

```

- Pretty much any time you are using `sleep()` in a script you are doing something wrong.
- You have `import time` inside a while loop. It should be outside so it only happens once (but you shouldn't be using `sleep()` anyway).
- You have an `else` statement with no `if`. (See @pascal.fragnoud's comment below.)
- `while value == True:` can be reduced to `while value:`.

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [July 18, 2024, 3:43pm UTC](https://forum.inductiveautomation.com/t/looping-a-script-while-tag-is-on/93402/3 "2024-07-18T15:43:05Z")

</div>

General principles for Ignition scripting:

1. Avoid `while` loops, they're almost never the answer.
2. Seriously, don't use sleep().
3. Avoid side effects in a transform. Transforms should only ever affect the `value` they are given, modify it in place, and return a new value.
4. Don't do anything slow in a transform, like calling out over the network or interacting with a database or, y'know, explicitly sleeping for thirty seconds.

Even more broad advice: **Don't use scripting if you don't have to**.

In this specific case, you don't need any scripting at all.  
If the `loop` property of the audio component is set to true, all you have to do is bind the `play` property to your tag:

> **[Perspective - Audio | Ignition User Manual](https://www.docs.inductiveautomation.com/docs/8.1/appendix/components/perspective-components/perspective-display-palette/perspective-audio)**
>
> An audio component that Designers can use to play and pause sound clips in the browser.

That's _literally it_. Perspective will automatically set up the subscription infrastructure, so as soon as the tag value changes, the binding will re-evaluate and the property will change. This is fundamental to how Perspective (and Vision) work and is _extremely_ important to learn.

If you actually need the behavior of "play the sound, looping as many times as possible whenever the tag goes true, but stop playing after thirty seconds", then you'll want an approach like @Transistor suggested above. But I'd definitely recommend simplifying the requirement.

---

<div class="post-metadata">

**Author:** ![pascal.fragnoud](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pascal.fragnoud/32/24728_2.png) [@pascal.fragnoud](https://forum.inductiveautomation.com/u/pascal.fragnoud)\
**Post date:** [July 18, 2024, 3:43pm UTC](https://forum.inductiveautomation.com/t/looping-a-script-while-tag-is-on/93402/4 "2024-07-18T15:43:39Z")

</div>

> [@Transistor](#):
>
> - You have an `else` statement with no `if`.

Python accepts `else` after loops. It's a weird construct that you don't see much, but it does exist.  
Basically, it runs the `except` if you didn't break out of the loop.

---

<div class="post-metadata">

**Author:** ![jason\_melcher](https://avatars.discourse-cdn.com/v4/letter/j/6bbea6/32.png) [@jason\_melcher](https://forum.inductiveautomation.com/u/jason_melcher)\
**Post date:** [July 18, 2024, 4:41pm UTC](https://forum.inductiveautomation.com/t/looping-a-script-while-tag-is-on/93402/5 "2024-07-18T16:41:20Z")

</div>

Thanks for the info but this was what I had mentioned in my comment. If I want to use the loop tag and add a 30 second delay between loops how would I do that?

I want it to play a 2 second sound clip, wait x seconds, then if the tag is still true play again.

I COULD add 30 seconds to the sound clip so its 2 seconds of sound, 30 seconds of silence and bind the loop to the tag but then i cant have control over the silence. I want to adjust the time between 2 second payments.

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [July 18, 2024, 4:56pm UTC](https://forum.inductiveautomation.com/t/looping-a-script-while-tag-is-on/93402/6 "2024-07-18T16:56:43Z")

</div>

1. Add a custom property (let's call it `condition`) to the sound component.
2. Bind `condition` to your condition tag.
3. Add a change script to this custom property. This change script writes the current time to an additional custom property, `timestamp`, using `system.date.now()`.
4. Bind the `play` property using an expression:

```python
{this.custom.condition} && secondsBetween({this.custom.timestamp}, now(1000)) % 30 <= 2

```

The `now(1000)` defines a poll rate for the expression, so it will automatically re-evaluate every second, check the condition(s), and return a result. No while loops needed.

The `30` and `2` values in the expression can be bound to any other arbitrary properties.

> **Component JSON**
>
> If you copy this to the clipboard you should be able to paste it in to a view as a self contained sample component:
> 
> ```json
> [
> {
> "type": "ia.display.audio",
> "version": 0,
> "props": {},
> "meta": {
> "name": "Audio"
> },
> "position": {
> "x": 317,
> "y": 101,
> "width": 300,
> "height": 55
> },
> "custom": {
> "timestamp": {
> "$": [
> "ts",
> 192,
> 1721321870088
> ],
> "$ts": 1721321870065
> }
> },
> "propConfig": {
> "custom.condition": {
> "binding": {
> "type": "tag",
> "config": {
> "mode": "direct",
> "tagPath": "[default]trigger",
> "fallbackDelay": 2.5,
> "publishInitial": false
> }
> },
> "onChange": {
> "script": "\tself.custom.timestamp = currentValue.timestamp",
> "enabled": null
> }
> },
> "props.play": {
> "binding": {
> "type": "expr",
> "config": {
> "expression": "{this.custom.condition} && secondsBetween({this.custom.timestamp}, now(1000)) % 30 <= 2"
> }
> }
> }
> }
> }
> ]
> 
> ```

---

<div class="post-metadata">

**Author:** ![jason\_melcher](https://avatars.discourse-cdn.com/v4/letter/j/6bbea6/32.png) [@jason\_melcher](https://forum.inductiveautomation.com/u/jason_melcher)\
**Post date:** [July 18, 2024, 5:10pm UTC](https://forum.inductiveautomation.com/t/looping-a-script-while-tag-is-on/93402/7 "2024-07-18T17:10:37Z")

</div>

wow, I have so much to learn. I don't understand half of that.

In another language I use I would simply do this  
\*top  
if tag(1)=true  
play sound clip #1  
timer (x)  
jump \*top  
else  
exit

and as soon as tag(10=true it would play, wait x seconds, and then check again....

thanks for the feedback / help. I will dig into what you posted and try to see what i can learn from that.

It's mind boggling to me that adding a 30 (or x) second delay between playing a sound could require this much code.

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [July 18, 2024, 5:19pm UTC](https://forum.inductiveautomation.com/t/looping-a-script-while-tag-is-on/93402/8 "2024-07-18T17:19:44Z")

</div>

> [@jason\_melcher](#):
>
> In another language

That is an example of **sequential** programming, with a task that doesn't need to do **anything** else at the same time.

Ignition has to do many thousands of things in any short interval. Sequential program design is an _ **utter disaster** _ for such systems.

Ignition is therefore almost entirely **event driven** , and events are expected to "do their thing" as quickly as possible and return, letting the next event do **its** thing in turn. Anything involving delays, in an event driven architecture, are about **scheduling** some event, or regularly **checking** for the end of the delay.

If you have experience with PLC real time tasks, they work much like the latter--every scan runs really quickly, and any timer instructions check every scan whether their time is up.

---

<div class="post-metadata">

**Author:** ![jason\_melcher](https://avatars.discourse-cdn.com/v4/letter/j/6bbea6/32.png) [@jason\_melcher](https://forum.inductiveautomation.com/u/jason_melcher)\
**Post date:** [July 18, 2024, 6:28pm UTC](https://forum.inductiveautomation.com/t/looping-a-script-while-tag-is-on/93402/9 "2024-07-18T18:28:27Z")

</div>

correct. Usually that sub program is running and loops constantly so its always running in the background.

I should have added the else = false would also jumped back to \*top and it would continually loop waiting for the tag to change.

essentially ignition has to be doing the same thing waiting for a PLC tag (input) to change right? wouldn't that be the same as waiting for an external input to turn on or off in a PLC?

Sorry... I know I have a lot of learning to do it just blows my mind on the complexity of what I would consider a simple task.

I still havent been able to get this to work and I feel like I've wasted half a day on it.

---

<div class="post-metadata">

**Author:** ![pturmel](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pturmel/32/3295_2.png) [@pturmel](https://forum.inductiveautomation.com/u/pturmel)\
**Post date:** [July 18, 2024, 6:36pm UTC](https://forum.inductiveautomation.com/t/looping-a-script-while-tag-is-on/93402/10 "2024-07-18T18:36:44Z")

</div>

> [@jason\_melcher](#):
>
> essentially ignition has to be doing the same thing waiting for a PLC tag (input) to change right?

No, not at all. Event driven systems have loops waiting for **any** event (of a certain kind), and hand off to the code that is set to handle it. PLC drivers schedule polling events to regularly read live data, and pass the results to Ignition's tag system. That tag system checks new value against prior value and injects appropriate new events for those into the correct loop's queue. Tag bindings in Ignition are _subscriptions_ to such tag events.

Simple loops that repeated check **one** condition are simply not acceptable in realtime or near-realtime systems.

If you used your sample code in a real PLC, it would fault, because unlimited looping ties up the task in which it occurs.

> [@jason\_melcher](#):
>
> still havent been able to get this to work

Paul gave you **the** answer. No script.

---

<div class="post-metadata">

**Author:** ![MMaynard](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mmaynard/32/3366_2.png) [@MMaynard](https://forum.inductiveautomation.com/u/MMaynard)\
**Post date:** [July 18, 2024, 6:39pm UTC](https://forum.inductiveautomation.com/t/looping-a-script-while-tag-is-on/93402/11 "2024-07-18T18:39:28Z")

</div>

You could use the SFC module for tihs. 🤷‍♂️

---

<div class="post-metadata">

**Author:** ![jason\_melcher](https://avatars.discourse-cdn.com/v4/letter/j/6bbea6/32.png) [@jason\_melcher](https://forum.inductiveautomation.com/u/jason_melcher)\
**Post date:** [July 18, 2024, 9:05pm UTC](https://forum.inductiveautomation.com/t/looping-a-script-while-tag-is-on/93402/12 "2024-07-18T21:05:11Z")

</div>

No he didnt give me the answer. I did ask how to use the loop property to make this work (because I understand that is what it is for) but with the looping property tag on it constantly loops. It becomes a 2 second loop I need x seconds in between loops.

that's the whole point of why i was using a time.sleep function which everyone is saying is bad but I still don't understand what else to use as a wait.

---

<div class="post-metadata">

**Author:** ![jason\_melcher](https://avatars.discourse-cdn.com/v4/letter/j/6bbea6/32.png) [@jason\_melcher](https://forum.inductiveautomation.com/u/jason_melcher)\
**Post date:** [July 18, 2024, 9:08pm UTC](https://forum.inductiveautomation.com/t/looping-a-script-while-tag-is-on/93402/13 "2024-07-18T21:08:47Z")

</div>

I do understand the last part about binding loop to play. I had originally just bound them both to the tag that turned on and off and it worked fine except it looped too quickly.

The issue is it constantly loops. there is no option to add a variable delay inbetween when it plays. thats the reason i was trying to use a script.

I play a 2 second sound clip "assistance needed station 10" and then i need 30,60,or 90 seconds before it plays again.

If there was an option "add x seconds between loops" I would have used that.

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [July 18, 2024, 9:53pm UTC](https://forum.inductiveautomation.com/t/looping-a-script-while-tag-is-on/93402/14 "2024-07-18T21:53:49Z")

</div>

The example I gave you above does what you're asking for, but in an event driven way.

Instead of one single script controlling everything (which means locking up an entire OS thread, busy-waiting for 30 seconds - hopefully you can see how that won't scale as you have more and more sessions running and more and more audio clips potentially playing), you're breaking down the control into smaller pieces of logic, and the only scripts and expressions involved will be event driven and complete in milliseconds.

I literally posted an example you can copy and paste directly into your project, but here's some more detail. There's exactly 3 things involved - one tag binding, one expression binding, and one property change script. All of them are basic, fundamental tools you'll have to become comfortable with in Perspective development.

In step 2, I'm just using a simple direct tag binding to my test boolean trigger tag:

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/b/b/bb83277018d54cb3afbeccabbd0db8423a237eb4.png)

In step 3, I'm right clicking the `condition` property and selecting 'Add Change Script':

 ![image](https://us1.discourse-cdn.com/inductiveautomation/original/3X/5/f/5f85f057b258fcfdd78cca6eb398605e54626c90.png)  
The script I'm adding is just this: Changing the value of the `timestamp` custom property to the new value that was just written to the tag. This should be ~identical to the current time, which you could also retrieve via `system.date.now()` - they're equivalent.

```python
def valueChanged(self, previousValue, currentValue, origin, missedEvents):
	self.custom.timestamp = currentValue.timestamp

```

In step 4, I'm adding that expression, the most complicated piece of this whole thing. Expressions are, [**by definition**](https://en.wikipedia.org/wiki/Expression_(computer_science)), simple units that unconditionally return _some_ value. In this case, we're only ever going to return `true` or `false`, because we're using this expression to control the `play` property.

```python
{this.custom.condition} 
&& secondsBetween({this.custom.timestamp}, now(1000)) % 15 <= 2

```

There's two elements to the expression  
`{this.custom.condition}` is just a reference to the existing custom property. This, combined with the subsequent `&&` operator, combines to a boolean logical AND operation - thus our expression will _always_ evaluate to `true` or `false`.

`secondsBetween({this.custom.timestamp}, now(1000)) % 30 <= 2`  
Walking this from the innermost piece out, you've got:

1. `now(1000)`. This just means the whole expression will poll at the given rate. This is the most "magic" piece of this operation, and generally falls into the "you just have to learn this" category. See also: [now | Ignition User Manual](https://www.docs.inductiveautomation.com/docs/8.1/appendix/expression-functions/date-and-time/now)
2. `secondsBetween({this.custom.timestamp}, now(1000))`. As the name implies, it's just giving you the integer seconds between two timestamps. This delta forms the basis of the following logic.
3. `% 30` is using the [modulo operator](https://www.docs.inductiveautomation.com/docs/8.1/platform/expression-language-and-syntax#operators) to give you the remainder of a division operation. So `1 mod 30 = 1`, `30 mod 30 == 0`, `31 mod 30 == 1`, `45 mod 30 == 15`, and so on. The end result is a constant sawtooth ramp - we go from 0 to 30, then drop back to 0.
4. `<= 2` then, check whether that modulo result is less than or equal to 2. Thus, for 2 seconds out of every 30, the second half of the expression will be true.

Here's the same logic, except with a 15 second modulo to make the recording a little shorter, and with all the intermediate logic broken out in separate custom properties:

---

<div class="post-metadata">

**Author:** ![jason\_melcher](https://avatars.discourse-cdn.com/v4/letter/j/6bbea6/32.png) [@jason\_melcher](https://forum.inductiveautomation.com/u/jason_melcher)\
**Post date:** [August 8, 2024, 3:23pm UTC](https://forum.inductiveautomation.com/t/looping-a-script-while-tag-is-on/93402/15 "2024-08-08T15:23:59Z")

</div>

Thank you so much for the detailed reply. I think I was frustrated before and haven't had a chance to get back to this until today. When you break it down into small parts it makes much more sense.

Also I didnt know that sleep put the whole thing to sleep for the time. I understand why not to use it now.

I was able to get the looping to work but it loops immediately with no wait in between. I tried adjust the different variables in the expression binded on play with no luck.

And since we are on the topic of the time inbetween loops lets assume I have a page called sound with 14 different sound components each playing a different audio file on it and depending on the tag that is tripped one of the audio files will play. Can I have a global variable for the entire page that I can change and it will change the delay for all 14 audio files so I dont need to add it to each sound component?

Furthermore on this topic - Is there a way I can make sure the audio files don't play over each other if they are tripped at the same time?

---

<div class="post-metadata">

**Author:** ![Felipe\_CRM](https://avatars.discourse-cdn.com/v4/letter/f/dfb087/32.png) [@Felipe\_CRM](https://forum.inductiveautomation.com/u/Felipe_CRM)\
**Post date:** [August 8, 2024, 3:59pm UTC](https://forum.inductiveautomation.com/t/looping-a-script-while-tag-is-on/93402/16 "2024-08-08T15:59:44Z")

</div>

> [@jason\_melcher](#):
>
> global variable for the entire page that I can change and it will change the delay

Make a custom prop at the root and then replace the hardcoded delay in each expression with a reference to that prop

```python
{this.custom.condition} 
&& secondsBetween({this.custom.timestamp}, now(1000)) % {root.custom.global_delay} <= 2

```

Actually, Paul mentioned it in his original reply:

> [@paul-griffith](#):
>
> The `30` and `2` values in the expression can be bound to any other arbitrary properties.

You might be able to have just one audio component with a bound source as well, but I don't know if that would introduce a significant buffering delay when swapping sources.

---

<div class="post-metadata">

**Author:** ![jason\_melcher](https://avatars.discourse-cdn.com/v4/letter/j/6bbea6/32.png) [@jason\_melcher](https://forum.inductiveautomation.com/u/jason_melcher)\
**Post date:** [August 8, 2024, 4:45pm UTC](https://forum.inductiveautomation.com/t/looping-a-script-while-tag-is-on/93402/17 "2024-08-08T16:45:42Z")

</div>

Thanks Felipe,

after my post I reread Paul's post and saw the part of the variable. I got that to work. Still not getting the looping to work with the delay but I'm still working on it.

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [August 8, 2024, 4:52pm UTC](https://forum.inductiveautomation.com/t/looping-a-script-while-tag-is-on/93402/18 "2024-08-08T16:52:52Z")

</div>

> [@jason\_melcher](#):
>
> Furthermore on this topic - Is there a way I can make sure the audio files don't play over each other if they are tripped at the same time?

Not easily.

---

<div class="post-metadata">

**Author:** ![nminchin](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/nminchin/32/6911_2.png) [@nminchin](https://forum.inductiveautomation.com/u/nminchin)\
**Post date:** [September 13, 2024, 9:34pm UTC](https://forum.inductiveautomation.com/t/looping-a-script-while-tag-is-on/93402/19 "2024-09-13T21:34:43Z")

</div>

> [@jason\_melcher](#):
>
> Is there a way I can make sure the audio files don't play over each other if they are tripped at the same time

Late to the party, but I wouldn't be adding 14 of these audio components, I would be adding one, and adding a binding for the source audio file path based on your conditions. Then by design, you will only ever play a single audio file

---

<div class="post-metadata">

**Author:** ![amarks](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/amarks/32/45610_2.png) [@amarks](https://forum.inductiveautomation.com/u/amarks)\
**Post date:** [September 16, 2024, 1:29pm UTC](https://forum.inductiveautomation.com/t/looping-a-script-while-tag-is-on/93402/20 "2024-09-16T13:29:30Z")

</div>

> [@playSoundClip Repeatedly](https://forum.inductiveautomation.com/t/playsoundclip-repeatedly/95345):
>
> A continuation of the original question on [this thread](https://forum.inductiveautomation.com/t/share-audio-clips-between-gateway-and-clients/95286). I can now share audio files between the gateway and the clients rather automatically via a client tag change script. I am also now able to play the file on the client via system.util.playSoundClip(). The question is now, since these are all clips of varying length, how do I get the sound clip to play repeatedly? I can imagine doing something rather archaic and toggling a client tag right after the playSoundClip call, which then triggers…

[Next page](https://forum.inductiveautomation.com/t/looping-a-script-while-tag-is-on/93402.md?page=2)
