Write Multiple OPC tags

Hello everyone,

I'm trying to read bulk 500 derived tags and write bulk OPC tags as fast as I can.

First, I tested reading Derived tags and writing into Memory tags.

  • Using system.tag.readBlocking and system.tag.writeBlocking - It took around 4s.
  • Using system.tag.readBlocking and system.tag.writeAsync - it took around 100ms (which is perfect!)

To measure I used system.date.now(). For writeBlocking I printed end time after execution, while to measure writeAsync I used a callback function to print end time.

Now, when writing to OPC tags

  • Using system.tag.readBlocking and system.tag.writeBlocking - It took around 50 - 60s.
  • Using system.tag.readBlocking and system.tag.writeAsync - it took around 40 - 50s
  • Using system.tag.readBlocking and system.opc.writeValues - it took around 30 - 40s

How can I improve this behaviour? I'm writing to an Open ModSim Server from Ignition 8.1.48 Edge.

The OPC connection has default configurations

OPC tags are configure with this Tag Group

No it didn't. You're not "Timing" what you think you're timing.

Fundamentally, the only difference between writeBlocking() and writeAsync(), is that the system doesn't wait around for the return telling you if the writes were successful or not. Instead firing the write command and returning instantly.

You only timing the execution time of the script that you wrote, which not suprisingly took longer when you blocked it from continuing.

Also, remeber when writing to OPC tags, system.tag.write*() doesn't actually write to the device.

This seems like forever, for 500 tags. My gut reaction to this execution time is that their is an issue with the script that you're using. Please share the script, as I really do not think these times have anything to do with the driver or tag configuration.

3 Likes

You might want to share some code and more about your Modbus setup (Addresses used, etc...)

1 Like

This is the code for writeAsync and OPC tags and how I measure execution the time

def update():
    # Browse UDT instances
    udt_name = "point_2v1"
    tag_provider = "[edge]"
    filter_tags = {"tagType":"UdtInstance","typeId":udt_name,"recursive":True}
    browse_results = system.tag.browse(tag_provider,filter_tags).getResults()
    
    #Define input/output tag paths
    udt_instances = [str(res["fullPath"]) for res in browse_results]    
    input_paths = [str(inst) + "/inputValue" for inst in udt_instances]
    output_paths = [str(inst) + "/outputValue" for inst in udt_instances]
    
    #Read input values
    write_values = system.tag.readBlocking(input_paths)
    values_list = [val.value for val in write_values]
    
    #Write output values
    system.tag.writeAsync(output_paths, write_values,myCallback)

def myCallback(asyncReturn):
	print ("End time: {} ms".format( system.date.now()))

# Run the function
import system
print("Start time: {} ms".format( system.date.now()))
update()

The Start time is printed on the script console while the End time is printed on the output console once the operation was complete. This time took 20s to write 480 OPC tags.

For writeBlocking I just replace this part of the code, and both Start/End time are printed on scripting console.

 #Write output values
 system.tag.writeBlocking(output_paths, write_values)
 print ("End time: {} ms".format(system.date.now()))

Modbus Setup Description Using Ignition OPC UA server:

The Modbus setup consists of a single Modbus TCP connection to a locally installed Open ModSim server. Both the Edge Gateway and the ModSim server are hosted on the same local machine.

The Edge Gateway is configured with 500 UDT instances. Each UDT instance contains two tags: one Derived tag and one OPC tag. These OPC tags correspond to Modbus Holding Registers (Int, Float, Double), mapped sequentially from address 1.

You're including the tag browse in your timing. Have you measured without that?

Yes! I've measured even without all for loops for tagPath creation and there's no big difference

Can you take a Wireshark capture?

This is more or less instant when I try it.

from java.lang import System

start = System.currentTimeMillis()

# Browse UDT instances
udt_name = "MbTest"
tag_provider = "[default]"
filter_tags = {"tagType":"UdtInstance","typeId":udt_name,"recursive":True}
browse_results = system.tag.browse(tag_provider,filter_tags).getResults()

print browse_results

#Define input/output tag paths
udt_instances = [str(res["fullPath"]) for res in browse_results]    
input_paths = [str(inst) + "/I" for inst in udt_instances]
output_paths = [str(inst) + "/O" for inst in udt_instances]

#Read input values
input_values = system.tag.readBlocking(input_paths)
output_values = [val.value for val in input_values]


#Write output values
print system.tag.writeBlocking(output_paths, output_values)

print ("Duration: {} ms".format(System.currentTimeMillis() - start))
>>> 
(snipped prints)
Duration: 190 ms
>>> 

That is amazing!

How many OPC tags are you writing? Are you using Ignition OCP UA Server? Could you share more details about your test please.

500 OPC tags from 500 instances of the following UDT, Ignition OPC UA server.

UDT definition:

This is my UDP config, the only differences I see

  1. Different data types (Integer, Float, Double) which means different modbus designators and not consecutive registers
  2. I'm using parameters.

While this is the OPC connection

When I run the same code as you, I have this result:
First, I check the initial values for HR1 and HR1103 in Ignition and Open ModSim Server.
Then executed the script and once it finishes check the new values HR1 and HR1103. This time took 33sec.

java_XQLba9TqMW

I try to configure each OPC tag as Integer and consecutive, but I see this error message on Gateway Logs

What software do you use to simulate a Modbus Device and create your connection? I only have experience with Open ModSim

Something custom based on my Modbus library.

This might mean you are writing to an address the simulator you're using doesn't consider valid.

It would help if you could get a Wireshark capture of the writes.

Ooh, or this could be a bug in the driver... I kind of remember something about the max number of registers to write actually being 123 and not 125...

Try setting the "Max Holding Registers Per Request" setting on the driver to 123 instead. The bug was that it's supposed to be 125 for reads and 123 for writes, but the driver only has one shared setting that is wrong for writes.

Huh. Never noticed this. (Mine has separate settings.)

Yeah... it should be two on ours as well.

It's rare that you ever have writes packed so well that you fill out a request, and then I think on top of that some servers will happily accept 125 registers in a write, because it does actually fit when you're doing Modbus TCP without any regard for the size limit inherited from the original serial implmentation. I think you can fit 127 before you overflow the "Byte Count" byte in the request.

But the spec limits it to 123 so the overall PDU/ADU doesn't exceed 256 bytes, even though that doesn't matter on Modbus TCP.

1 Like

Thank you for your fast response!

After setting Max Holding Registers Per Request to 123

  • Writing to 500 OPC tags (Integer, Float, Double) with no consecutive registers - took 42sec.
  • Writing to 500 Integer OPC tags with consecutive registers - took 3.7 sec (it stills high)

I will try to get Wireshark capture

Too bad this is Edge. You could use my Integration Toolkit's Republish action to do this without a script. Unless you buy my alternate Modbus driver for Edge, which gives me an opportunity to include the Toolkit....

/shameless plug

:grin:

Is there any other way to pass the inputValue to the outputValue without scripting but using derived tags ?

Not that I'm aware of. That's why I added Tag Actions to my toolkit module.

Ever get that Wireshark capture?

Even when I modify mine so the registers have gaps and the driver has to do 500 separate Write Single Register requests it only takes 500-600ms.

It's my first time using Wireshark, so let me know if it's okay.

Now I'm using Modbus Slave instead of Open ModSim which improved writing to 17sec.
After running wireshark I only saw Write Single Register requests.
Test 1.pcapng (841.0 KB)

During Test 2 I enabled Force Multiple Register Writes but it took 17sec as well.
Test 2.pcapng (720.6 KB)