OPC tag write from DB table to OPC UDT data type

Hi guys, I have a UDT tag in the PLC Data[99] and each memeber is also a UDT data type so for example data[0] has Data[0].name ,Data[0].code, Data[0].level
I also have a table that has 4 columns name,code,level and Tag(I put Data[0],Data[1],Data[2] etc… in this column)
what I’m trying to do is to write all the columns name, code,level to the tags at the same time.
The following should work for writing to Data[0]

name_0 = [PlcPath]Global.Data.Data[0].name
code_0 = [PlcPath]Global.Data.Data[0].code
level_0 = [PlcPath]Global.Data.Data[0].level
db_name_0 = 'from query' 
db_code_0 = 'from query'
db_level_0 = 'from query'

system.opc.writeValues(	"IgnitionOPCUA", 	[name_0, code_0, level_0], [db_name_0, db_code_0, db_level_0])

I’d like to know how I could go about doing this for all 100 of them all together? some kind of loop I suppose? :thinking::nerd:

Something like this might work depending on how the queries or better yet, the result from a single query can be indexed .

writes = {}

for i in range(100):
    writes["[PlcPath]Global.Data.Data[%s].name" % (i)] = 'from query'
    writes["[PlcPath]Global.Data.Data[%s].code" % (i)] = 'from query'
    writes["[PlcPath]Global.Data.Data[%s].level" % (i)] = 'from query'

system.opc.writeValues("IgnitionOPCUA", writes.keys(), writes.values())

Looks like it should work. I’m gonna try it when I get in the office.
As far as indexes what will happen is I will only have 100 entries, primary key will be from 1-100 and I will set it up so they can’t add any more rows when all 100 rows are reached. There only thing I have to do is use is i-1 instead of I since for I in (100) starts from 1 correct? I want it to start from 0.
Thanks for the suggestion!

The[quote=“Mr.K001, post:3, topic:17452”]
since for I in (100) starts from 1 correct?
[/quote]

That is not correct, it will go from 0-99

To extend my answer you might want to do something like this then

writes = {}

qRes = system.db.runQuery("SELECT * FROM your_table")

for i in range(qRes.rowCount):
    rid = qRes.getValueAt(i, 'id')
    writes["[PlcPath]Global.Data.Data[%s].name" % (rid - 1)] = qRes.getValueAt(i, 'name')
    writes["[PlcPath]Global.Data.Data[%s].code" % (rid - 1)] = qRes.getValueAt(i, 'code')
    writes["[PlcPath]Global.Data.Data[%s].level" % (rid - 1)] = qRes.getValueAt(i, 'level')

system.opc.writeValues("IgnitionOPCUA", writes.keys(), writes.values())
1 Like

It shouldn’t matter in this case, but keep in mind that jython dictionaries are based on hashes, and don’t ensure the items are returned in any specific order. I always build such operations with two lists, one for paths and the other for values, and append to them inside the loop.

1 Like

Can’t find anything in the jython docs, but found this in the python 2.x docs. Normally I do it this way and it haven’t failed until now. Remember looking this up the first time I did it.

If items(), keys(), values(), iteritems(), iterkeys(), and itervalues() are called with no intervening modifications to the dictionary, the lists will directly correspond. This allows the creation of (value, key) pairs using zip(): pairs = zip(d.values(), d.keys()). The same relationship holds for the iterkeys() and itervalues() methods: pairs = zip(d.itervalues(), d.iterkeys()) provides the same value for pairs. Another way to create the same list is pairs = [(v, k) for (k, v) in d.iteritems()].

Yes, that’s why I said it didn’t matter in this case. But the order won’t be 0-99. Order can matter at the driver level, especially if the max packet size is exceeded after optimization.

So I tried this and I wasn’t getting any errors but at the same time I didn’t see any changes on the PLC side so I tried to see what i get returned and I get this

[Bad_InternalError] An internal error occurred as a result of a programming or configuration error

I ran a test just to see if I’m able to write those the tags and the following works just fine

> system.tag.write("Test/DATA_2_/NAME/STRING", 'Test')
> system.tag.write("Test/DATA_2_/CODE", 11111)
> system.tag.write("Test/DATA_2_/LEVEL", 3)

one thing tho, I had to remove return in

else:
	return

and just go with

else:
	system.opc.writeValues(server, writes.keys(), writes.values())

since I was getting an error on return for being outside of the function.
I even went as far as doing one single write to Data[2].CODE and still returned bad internal error… any thoughts on this?

Update:
not sure why my test failed when I tried to write to DATA[2].CODE because I ran it again and it worked fine… looks like the only one I get the bad internal error is when I try to write to DATA[2].Name
Looking on the PLC side Name has NAME.LEN and NAME.DATA is it possible I have to specify NAME.DATA and write to that instead? I mean NAME.DATA is an ASCII data type so i dont see how that would work unless I convert my string to Ascii?

Add /STRING just like in your tests.

1 Like

I just figured it out … DATA[2].NAME.STRING looking at the tag path in my tag browser
Thanks @pturmel

Now all I have to do is write the same stuff to 24 different PLCs lol and I’m done :sunglasses:

Don’t actually know why I wrote that, it doesn’t make sense :slight_smile: Just remove the else part entirely and stick with the OPC write.

Hey guys, quick question, I’m going to have a setup as followed:

writes = {}
server = "Ignition OPC-UA Server"
qRes = system.db.runQuery("SELECT * FROM tableName",'DB Name')
	
for i in range(qRes.rowCount):
	rid = qRes.getValueAt(i, 'id')
	#Write to PLC 1#
	writes["[PLC1]Global.tagPath[%s].NAME.STRING" % (rid - 1)] = qRes.getValueAt(i, 'NAME')
	writes["[PLC1]Global.tagPath[%s].CODE" % (rid - 1)] = qRes.getValueAt(i, 'CODE')
	writes["[PLC1]Global.tagPath[%s].SECURITY_LEVEL" % (rid - 1)] = qRes.getValueAt(i, 'SECURITY_LEVEL')
	#Write to PLC 2#
    writes["[PLC2]Global.tagPath[%s].NAME.STRING" % (rid - 1)] = qRes.getValueAt(i, 'NAME')
	writes["[PLC2]Global.tagPath[%s].CODE" % (rid - 1)] = qRes.getValueAt(i, 'CODE')
	writes["[PLC2]Global.tagPath[%s].SECURITY_LEVEL" % (rid - 1)] = qRes.getValueAt(i, 'SECURITY_LEVEL')
  
    .
    .
    .

	writes["[PLC20]Global.tagPath[%s].NAME.STRING" % (rid - 1)] = qRes.getValueAt(i, 'NAME')
	writes["[PLC20]Global.tagPath[%s].CODE" % (rid - 1)] = qRes.getValueAt(i, 'CODE')
	writes["[PLC20]Global.tagPath[%s].SECURITY_LEVEL" % (rid - 1)] = qRes.getValueAt(i, 'SECURITY_LEVEL')
	system.opc.writeValues(server, writes.keys(), writes.values())    

What I’d like to figure out is what is the best way to write these, should I just do one writes{} for all or would it be better if I have writes1{} writes2{} etc. and write by PLC? or would it not even make a difference?
Reason I’m asking is I tried to write to 5 PLCs each of them have 50 tag Name,Code and Security level needs to be written to and it took a good 3-4 minutes for this to happen and during the whole thing the Ignition client was unresponsive and it only came back to life after the process was finished so I wanted to make sure I do this the best way possible.

system.opc.writeValues should only be called once. From your (broken) indentation, it appears that your are calling it for every row. Also, anything that takes more than a fraction of a second in an event routine should be pushed into a separate functions and called with system.util.invokeAsynchronous.

3 Likes

You are correct sir, that broken indentation shouldn’t have been there, it only took 10-15 sec which is much faster than before.
I’ll look into your suggestion using a function with system.util.invokeAsynchronous.
Thanks

Also worth pointing out - although I’ll leave this as an exercise for the reader - that there’s no reason to specify [PLC1], [PLC2], etc in the code - if they’re perfectly consistent then it’s great practice to have another loop to go through the 20 values.

Yeah unfortunately they aren’t exactly PLC1 PLC2, I just used that as an example, The names are different.

1 Like