Delay between lines of code

Good morning, I am trying to make sure that when a button is clicked certain values ​​are saved in the database, there is a wait of 5 seconds for the data to be saved correctly in the database.

After 5 seconds, a report is downloaded or generated with the data obtained.

I have tried to do it by scripting, but when clicking on the button, the data is saved in the database but the report is not generated, the problem I find is the 5-second delay. I don't know how to generate it correctly.

I've read that time.sleep() and similar functions should not be used in Ignition, so I'm looking for a way to generate this delay.

ps: I also tried to use the tags alarm function to add a delay but it didn't work.

Any kind of help is appreciated!

I wouldn't use a delay for this. You should try to query for the data and wait until you get it back or sometimes using getkey=True is enough for a runPrepUpdate to verify that the query completed.

If you don't want to use handshaking. You can use the sleep() function, but you will want to use invoke functions to run it. You can also try system.util.invokeLater() and eliminate the timer all together.

What do you mean the issue is a 5 second delay? Can you post the code on your button? I will say a delay is almost never the answer and covering up some other issue.


Tag = system.tag.readBlocking("[Kenmex]Kenmex/Transaction Groups Triggers/Kenmex Monitoreo/Kenmex Monitoreo Trigger_Click")[0].value
    Delay = system.tag.readBlocking("[Kenmex]Kenmex/Transaction Groups Triggers/Kenmex Monitoreo/Delay/Alarms/Alarm.IsActive")[0].value
        if event.path[0] == 0 and event.path[1] == 6 and event.path[2] == 1 and event.path[3] == 0:
            system.tag.writeBlocking("[Kenmex]Kenmex/Transaction Groups Triggers/Kenmex Monitoreo/Kenmex Monitoreo Trigger_Click", True)
            system.tag.writeBlocking("[Kenmex]Kenmex/Transaction Groups Triggers/Kenmex Monitoreo/Delay", True)

        if Delay == True and Tag == False:
            today =
            data ="assy_kenmex/Kenmex Monitoreo/Kenmex Monitoreo_Click", fileType = "pdf")
  "Kenmex Monitoreo_"+(, "MM-dd-yy_hh-mm a"))+".pdf", data)
            system.tag.writeBlocking("[Kenmex]Kenmex/Transaction Groups Triggers/Kenmex Monitoreo/Delay", False)

No, not even on a background thread. There is rarely(if ever) need to actually pause execution.

All of the functions:


Are blocking functions so the next line will not be executed until they return.

If you have a long running task, then you should use system.util.invokeAsyncronous() to run the task in the background. Then you can safely use system.util.invokeLater() to update the UI once the task has completed.


Personally, 5 seconds sounds like an eternity for a DB to return from a query, but then I don't know what your query is doing.


Where do you run your query? I don't see anything here in your script. Are these things happening in two different events?

1 Like

Looks like they're attempting to use a script to trigger a transaction group execution.


Good eye.

@Josue_Cuadras can you confirm exactly what semantic purpose should be? When someone triggers the transaction group, you want to create a report based on that transaction group, but the issue is it's creating the report immediately so there's no data from the transaction group because it hasn't executed at all yet? And you want like a few seconds of data in that report?

1 Like

Yes when the button is clicked the script activates a tag that triggers the Transaction Group, the problem is that when generating the report, the data that has just been acquired by pressing the button is not shown in the report, previous data from the database is shown but not the newly acquired data.


I will say seems like an odd pattern, I would expect that if I press a button for a report the report will only have data timestamped before my button push. But I guess your transaction group is off until the button push and then you want a minimal amount of data from a few executions.

I would say use system.util.invokeLater but I think that's only for vision. Perhaps @lrose has a better idea, it has been a while since I've touched perspective.

1 Like

As Phil already mentioned - if you want control over execution, scheduling, and to have a guarantee when something is done, considering dropping the transaction group entirely in favor of a script:


I'm pretty new to Ignition and SQL, is there any document or guide to create a table in SQL via script?


Do you actually need to create a table? Or do you just need to insert records into the existing table? You can even put them into the same table created by the transaction group.

1 Like

Yes to put the information inside the same table created by the Transaction Group, but I would also like to learn how to create a table from scratch independent of the transaction groups.


You need to find some tutorials for whatever brand of database you're using. Teaching you basic database use is really out of scope for this forum.

1 Like

Good place to start - SQL Tutorial

but as @Transistor mentioned while SQL often comes up here this is not a sql centric forum.

1 Like

Thanks to all of you, I started learning SQL and I was able to INSERT data into the table via a script, this script also generates a report with the captured data and when the report it’s generated the script deletes the data off the table:

def runAction(self, event):
      if event.path[0] == 0 and event.path[1] == 6 and event.path[2] == 1 and event.path[3] == 0:
            t_stamp =
            CV1_M_TEO = system.tag.readBlocking("[Kenmex]Kenmex/Axle CV1/stopcond/Matutino/TEO_TP/TEOTOTALMIN")[0].value
            CV1_M_TP = system.tag.readBlocking("[Kenmex]Kenmex/Axle CV1/stopcond/Matutino/TEO_TP/TPTOTALMIN")[0].value
            CV1_V_TEO = system.tag.readBlocking("[Kenmex]Kenmex/Axle CV1/stopcond/Vespertino/TEO_TP/TPTOTALMIN")[0].value
            CV1_V_TP = system.tag.readBlocking("[Kenmex]Kenmex/Axle CV1/stopcond/Vespertino/TEO_TP/TEOTOTALMIN")[0].value
            CV1_TEO_TOTAL = system.tag.readBlocking("[Kenmex]Kenmex/Axle CV1/stopcond/TEO_TP_TOTALTURNO/TEOTOTALMIN")[0].value
            CV1_TP_TOTAL = system.tag.readBlocking("[Kenmex]Kenmex/Axle CV1/stopcond/TEO_TP_TOTALTURNO/TPTOTALMIN")[0].value
            CV2_M_TEO = system.tag.readBlocking("[Kenmex]Kenmex/CV 2/stopcond/Matutino/TEO_TP/TEOTOTALMIN")[0].value
            CV2_M_TP = system.tag.readBlocking("[Kenmex]Kenmex/CV 2/stopcond/Matutino/TEO_TP/TPTOTALMIN")[0].value
            CV2_V_TEO = system.tag.readBlocking("[Kenmex]Kenmex/CV 2/stopcond/Vespertino/TEO_TP/TEOTOTALMIN")[0].value
            CV2_V_TP = system.tag.readBlocking("[Kenmex]Kenmex/CV 2/stopcond/Vespertino/TEO_TP/TPTOTALMIN")[0].value
            CV2_TEO_TOTAL = system.tag.readBlocking("[Kenmex]Kenmex/CV 2/stopcond/TEO_TP_TOTALTURNO/TEOTOTALMIN")[0].value
            CV2_TP_TOTAL = system.tag.readBlocking("[Kenmex]Kenmex/CV 2/stopcond/TEO_TP_TOTALTURNO/TPTOTALMIN")[0].value
            CV3_M_TEO = system.tag.readBlocking("[Kenmex]Kenmex/CV 3/stopcond/Matutino/TEO_TP/TEOTOTALMIN")[0].value
            CV3_M_TP = system.tag.readBlocking("[Kenmex]Kenmex/CV 3/stopcond/Matutino/TEO_TP/TPTOTALMIN")[0].value
            CV3_V_TEO = system.tag.readBlocking("[Kenmex]Kenmex/CV 3/stopcond/Vespertino/TEO_TP/TEOTOTALMIN")[0].value
            CV3_V_TP = system.tag.readBlocking("[Kenmex]Kenmex/CV 3/stopcond/Vespertino/TEO_TP/TPTOTALMIN")[0].value
            CV3_TEO_TOTAL = system.tag.readBlocking("[Kenmex]Kenmex/CV 3/stopcond/TEO_TP_TOTALTURNO/TEOTOTALMIN")[0].value
            CV3_TP_TOTAL = system.tag.readBlocking("[Kenmex]Kenmex/CV 3/stopcond/TEO_TP_TOTALTURNO/TPTOTALMIN")[0].value
            CV5_M_TEO = system.tag.readBlocking("[Kenmex]Kenmex/CV5/stopcond/Matutino/TEO_TP/TEOTOTALMIN")[0].value
            CV5_M_TP = system.tag.readBlocking("[Kenmex]Kenmex/CV5/stopcond/Matutino/TEO_TP/TPTOTALMIN")[0].value
            CV5_V_TEO = system.tag.readBlocking("[Kenmex]Kenmex/CV5/stopcond/Vespertino/TEO_TP/TEOTOTALMIN")[0].value
            CV5_V_TP = system.tag.readBlocking("[Kenmex]Kenmex/CV5/stopcond/Vespertino/TEO_TP/TPTOTALMIN")[0].value
            CV5_TEO_TOTAL = system.tag.readBlocking("[Kenmex]Kenmex/CV5/stopcond/TEO_TP_TOTALTURNO/TEOTOTALMIN")[0].value
            CV5_TP_TOTAL = system.tag.readBlocking("[Kenmex]Kenmex/CV5/stopcond/TEO_TP_TOTALTURNO/TPTOTALMIN")[0].value
            CV6_M_TEO = system.tag.readBlocking("[Kenmex]Kenmex/CV 6/stopcond/Matutino/TEO_TP/TEOTOTALMIN")[0].value
            CV6_M_TP = system.tag.readBlocking("[Kenmex]Kenmex/CV 6/stopcond/Matutino/TEO_TP/TPTOTALMIN")[0].value
            CV6_V_TEO = system.tag.readBlocking("[Kenmex]Kenmex/CV 6/stopcond/Vespertino/TEO_TP/TEOTOTALMIN")[0].value
            CV6_V_TP = system.tag.readBlocking("[Kenmex]Kenmex/CV 6/stopcond/Vespertino/TEO_TP/TPTOTALMIN")[0].value
            CV6_TEO_TOTAL = system.tag.readBlocking("[Kenmex]Kenmex/CV 6/stopcond/TEO_TP_TOTALTURNO/TEOTOTALMIN")[0].value
            CV6_TP_TOTAL = system.tag.readBlocking("[Kenmex]Kenmex/CV 6/stopcond/TEO_TP_TOTALTURNO/TPTOTALMIN")[0].value
            CV7_M_TEO = system.tag.readBlocking("[Kenmex]Kenmex/CV 7/stopcond/Matutino/TEO_TP/TEOTOTALMIN")[0].value
            CV7_M_TP = system.tag.readBlocking("[Kenmex]Kenmex/CV 7/stopcond/Matutino/TEO_TP/TPTOTALMIN")[0].value
            CV7_V_TEO = system.tag.readBlocking("[Kenmex]Kenmex/CV 7/stopcond/Vespertino/TEO_TP/TEOTOTALMIN")[0].value
            CV7_V_TP = system.tag.readBlocking("[Kenmex]Kenmex/CV 7/stopcond/Vespertino/TEO_TP/TPTOTALMIN")[0].value
            CV7_TEO_TOTAL = system.tag.readBlocking("[Kenmex]Kenmex/CV 7/stopcond/TEO_TP_TOTALTURNO/TEOTOTALMIN")[0].value
            CV7_TP_TOTAL = system.tag.readBlocking("[Kenmex]Kenmex/CV 7/stopcond/TEO_TP_TOTALTURNO/TPTOTALMIN")[0].value
            KENMEX_TEO_TOTAL = system.tag.readBlocking("[Kenmex]Kenmex/Kenmex Monitoreo/Kenmex Monitoreo Totales/TEO_TOTALMIN")[0].value
            KENMEX_TP_TOTAL = system.tag.readBlocking("[Kenmex]Kenmex/Kenmex Monitoreo/Kenmex Monitoreo Totales/TP_TOTALMIN")[0].value
            Paso = False

        FILAS_INSERTADAS = system.db.runPrepUpdate(CONSULTA, VALORES)
        if FILAS_INSERTADAS > 0:
            Paso = True
            Paso = False
        def GENERAR_REPORTE():
            data ="assy_kenmex/Kenmex Monitoreo/Kenmex Monitoreo_Click", fileType = "pdf")
  "Kenmex Monitoreo_"+(, "MM-dd-yy_hh-mm a"))+".pdf", data)
        if Paso == True:
            Paso1 = True

        def ELIMINAR_DATOS():
            CONSULTA1 = "DELETE FROM assy_kenmex_monitoreo_click; TRUNCATE TABLE assy_kenmex_monitoreo_click"
            ELMINAR = system.db.runPrepUpdate(CONSULTA1)
        if Paso1 == True:

Thank you all for your help!!

1 Like

Glad you were able to setup it with a SQL query instead of a transaction group, this is a much more sensible way to do things.

Some notes on your script if you care -

  1. You use read blocking, but you read each tag individually. It will be more performant to read all your tags at once with a list ie tagValues = system.tag.readBlocking(["tag1", "tag2", ...]) and extract from there. If you read them in the exact order you want to put them into your query, you can use a list comprehension and prepare them immediately for your system.db.runPrepUpdate.

  2. You execute report without any parameters which scares me. It scares me because I've had to deal with similar in the past, and if there is ever comes a time you have to re-create a report from the past, you are probably going to have trouble because your data sources are not using any parameters and are probably just grabbing the last row of your table. I would recommend using at least one parameters, most likely the id of row in your table you are making the report about.

  3. You can get the idea of your table from the moment you insert it. Do something like newRecord = = system.db.runPrepUpdate(CONSULTA, VALORES, getKey=1). Then newRecord will be the id of your newly created record and then I would think you could use that for your report parameter {"id":newRecord}. This also means you do NOT need to do

            Paso = True
            Paso = False

because either newRecord will give you the newId, or there will be some error. Either way the script will only proceed if you have the information you need or error out if something went wrong.

It seems like you just insert the record, make the report, and then clear the entire table. I would recommend against this as you are making it impossible to ever re-create an old report, your logic is full of "side effects" ie if you try to run the same report twice in a row, you cannot do it. This is the headache I experienced when dealing with a report that only created from data the moment in time and I had to redo it in the way I'm describing to you. It's better to aim for "pure" deterministic functions where the same function with the same parameters gives the same output every time.

  1. No need for an inner function like this
            CONSULTA1 = "DELETE FROM assy_kenmex_monitoreo_click; TRUNCATE TABLE assy_kenmex_monitoreo_click"
            ELMINAR = system.db.runPrepUpdate(CONSULTA1)

because its not called from anything like a invokeAsychronous or used as a first class object and passed elsewhere etc. Your logic can just be the lines you need to run right in a row.

  1. If you change the above, use an id as your report parameter to grab the right rows worth of data to use to make the report, then you no longer need to delete all the rows from your table either with
    CONSULTA1 = "DELETE FROM assy_kenmex_monitoreo_click; TRUNCATE TABLE assy_kenmex_monitoreo_click" Also, seems a bit redundant as both delete from without a where clause and truncate will clear the table of all rows. It's also a bit odd outside of situations where you use tables to hold temporary information to clear the whole table. Which is how you're treating the table now - but I suggest you don't for a db table used to generate reports.

I'm sure it works now, this is just some tips and hints from my experience with using reports that were not deterministic and pulling my hair out whenever I had to recreate one for the customer until I redid the whole structure eventually (after I learned everything I just told you).


You should read all your tags at once.

Modified code.
            tags = [
                "[Kenmex]Kenmex/Axle CV1/stopcond/Matutino/TEO_TP/TEOTOTALMIN",
                "[Kenmex]Kenmex/Axle CV1/stopcond/Matutino/TEO_TP/TPTOTALMIN",
                "[Kenmex]Kenmex/Axle CV1/stopcond/Vespertino/TEO_TP/TPTOTALMIN",
                "[Kenmex]Kenmex/Axle CV1/stopcond/Vespertino/TEO_TP/TEOTOTALMIN",
                "[Kenmex]Kenmex/Axle CV1/stopcond/TEO_TP_TOTALTURNO/TEOTOTALMIN",
                "[Kenmex]Kenmex/Axle CV1/stopcond/TEO_TP_TOTALTURNO/TPTOTALMIN",
                "[Kenmex]Kenmex/CV 2/stopcond/Matutino/TEO_TP/TEOTOTALMIN",
                "[Kenmex]Kenmex/CV 2/stopcond/Matutino/TEO_TP/TPTOTALMIN",
                "[Kenmex]Kenmex/CV 2/stopcond/Vespertino/TEO_TP/TEOTOTALMIN",
                "[Kenmex]Kenmex/CV 2/stopcond/Vespertino/TEO_TP/TPTOTALMIN",
                "[Kenmex]Kenmex/CV 2/stopcond/TEO_TP_TOTALTURNO/TEOTOTALMIN",
                "[Kenmex]Kenmex/CV 2/stopcond/TEO_TP_TOTALTURNO/TPTOTALMIN",
                "[Kenmex]Kenmex/CV 3/stopcond/Matutino/TEO_TP/TEOTOTALMIN",
                "[Kenmex]Kenmex/CV 3/stopcond/Matutino/TEO_TP/TPTOTALMIN",
                "[Kenmex]Kenmex/CV 3/stopcond/Vespertino/TEO_TP/TEOTOTALMIN",
                "[Kenmex]Kenmex/CV 3/stopcond/Vespertino/TEO_TP/TPTOTALMIN",
                "[Kenmex]Kenmex/CV 3/stopcond/TEO_TP_TOTALTURNO/TEOTOTALMIN",
                "[Kenmex]Kenmex/CV 3/stopcond/TEO_TP_TOTALTURNO/TPTOTALMIN",
                "[Kenmex]Kenmex/CV 6/stopcond/Matutino/TEO_TP/TEOTOTALMIN",
                "[Kenmex]Kenmex/CV 6/stopcond/Matutino/TEO_TP/TPTOTALMIN",
                "[Kenmex]Kenmex/CV 6/stopcond/Vespertino/TEO_TP/TEOTOTALMIN",
                "[Kenmex]Kenmex/CV 6/stopcond/Vespertino/TEO_TP/TPTOTALMIN",
                "[Kenmex]Kenmex/CV 6/stopcond/TEO_TP_TOTALTURNO/TEOTOTALMIN",
                "[Kenmex]Kenmex/CV 6/stopcond/TEO_TP_TOTALTURNO/TPTOTALMIN",
                "[Kenmex]Kenmex/CV 7/stopcond/Matutino/TEO_TP/TEOTOTALMIN",
                "[Kenmex]Kenmex/CV 7/stopcond/Matutino/TEO_TP/TPTOTALMIN",
                "[Kenmex]Kenmex/CV 7/stopcond/Vespertino/TEO_TP/TEOTOTALMIN",
                "[Kenmex]Kenmex/CV 7/stopcond/Vespertino/TEO_TP/TPTOTALMIN",
                "[Kenmex]Kenmex/CV 7/stopcond/TEO_TP_TOTALTURNO/TEOTOTALMIN",
                "[Kenmex]Kenmex/CV 7/stopcond/TEO_TP_TOTALTURNO/TPTOTALMIN",
                "[Kenmex]Kenmex/Kenmex Monitoreo/Kenmex Monitoreo Totales/TEO_TOTALMIN",
                "[Kenmex]Kenmex/Kenmex Monitoreo/Kenmex Monitoreo Totales/TP_TOTALMIN"
            value = system.tag.readBlocking(tags)

This information that I’m deleting is actually stored in another table every day at the end of a shift this informations is stored, this just a function to generate a temporarily report.

Some of the engineers here actually needed this temporarily reports, other than that this information is stored at the end of every shift.

About the reading of the tags, I will need to make that change.