How to set the tag quality in Python scripting in the WebDev module?

Hello,

Is there a way to set the tag quality? In particular in Python scripting in the WebDev module.

https://docs.inductiveautomation.com/display/DOC81/Quality+Codes+and+Overlays

I was thinking of using the following system function but could not find a way.

https://docs.inductiveautomation.com/display/DOC81/system.tag.configure

The idea is that an external system will set the tag quality to bad (or uncertain) based on external conditions.

Thanks,

Simon.

You can pass a qualified value to the system.tag.write* functions.
You can make a qualified value from BasicQualifiedValue, which takes up to 3 arguments: a value, a quality, and a timestamp.
If you pass one of those to a system.tag.write* function, only the specified arguments will be used to modify the tag.

from com.inductiveautomation.ignition.common.model.values import BasicQualifiedValue, QualityCode

qval = BasicQualifiedValue(quality=QualityCode.Bad)
system.tag.writeBlocking([tag_path], [qval])
4 Likes

Thanks!

I would like to create a generic API that can be used to set tag value, quality etc.

If I display qval I get the following value:

qval = '{ "quality": { "code": -2147483136 }, "timestamp": "May 2, 2022, 10:08:12 AM" }'

Is it also possible to do something like: (and set the qval in the body)

qval = '{ "quality": { "code": -2147483136 }, "timestamp": "May 2, 2022, 10:08:12 AM" }'
system.tag.writeBlocking([tag_path], [qval])

This does not seem to work?

That’s not a valid format for a qualified value. Build it using BasicQualifiedValue() instead.
There’s already a generic function that allows setting values, qualities and timestamp: system.tag.write* functions ;p

Wait, I’m sorry, I think I was wrong: Passing a qval without a value doesn’t retain the initial value of the tag. I thought I tested this, but it seems I missed something.
I just tried it again, and forcing a bad quality on a tag, then forcing it back to good, results in a null value.

You’ll have to read the tag’s value and pass it to BasicQualifiedValue(value, quality)

1 Like

Ah right, thanks for testing. Apart from this…

So there is no way to pass the quality to writeBlocking as a JSON value so I can pass it into the body? I always have to use BasicQualifiedValue?

I had this generic API defined on WebDev and was hoping I could also use it to set the quality:

	paths = request['data']['paths']
	values = request['data']['values']

	result = system.tag.writeBlocking(paths, values)
	
	return {'json': result}

Body:

{
    "paths": [
        "[SolidRed]/TT-111-11-11/ARF8200AA/DeviceEUI",
        "[SolidRed]/TT-111-11-11/ARF8200AA/HealthOK",
        "[SolidRed]/TT-111-11-11/ARF8200AA/LastSeen",
        "[SolidRed]/TT-111-11-11/ARF8200AA/MeasuredCurrentMicroA"
        ],
    "values": [
        "TT6482921",
        false,
        "2012-04-21 18:25:43",
        "2892.1519"
        ]
}

Import BasicQualifiedValue and QualityCode in your function, pass the wanted quality name in your body’s json, then change your function slightly:

paths = request['data']['paths']
values = request['data']['values']
qualities = request['data']['qualities']
qvals = [BasicQualifiedValue(value, getattr(QualityCode, quality, 'Uncertain')) for value, quality in zip(values, qualities)]

system.tag.writeBlocking(paths, qvals)
1 Like

No.

Yes. Or any other specialized implementation of QualifiedValue you might encounter.

The whole point of the WebDev module is to translate web requests (where json is popular) to Ignition's java APIs (where json is fringe), and to translates results from java APIs back to web-friendly formats. Ignition is fundamentally Java, not javascript.

1 Like

BTW

About the null value when the quality is set to BAD:

https://opcfoundation.org/forum/opc-ua-standard/usage-of-bad-opcqualitycode/

I think this is intended behavior…

It might, but this is not always the required behavior… In which case, passing a value when constructing the qualified value does the job.
I do it for an array of different qualities, some of which aren’t plain bad and need to be able to be restored to good and retain their original values.

Yes, I guess you are right. I just noticed that if you set the quality to UNCERTAIN, the value is also set to null.

Any idea how I can get the current values and feed them to the write?

Really sorry for asking… And about my Python skills… This is getting awkward… :crazy_face:

	paths = request['data']['paths']
	values = system.tag.readBlocking(paths)
	qualities = request['data']['qualities']
	qvals = [BasicQualifiedValue(value, getattr(QualityCode, quality, 'Uncertain')) for value, quality in zip(values, qualities)]
	
	result = system.tag.writeBlocking(paths, qvals)

	return {'json': result}

The QualifiedValue interface defines a derive(QualityCode) method, so you can construct your new QVs like this:

	qualities = request['data']['qualities']
	qvals = [qv.derive(getattr(QualityCode, quality, 'Uncertain')) for qv, quality in zip(values, qualities)]

derive() copies the current value into the new QualifiedValue object.

1 Like

Is that any different than building a qualified value and passing it the value directly ?

qval = BasicQualifiedValue(value, quality)

In your example, the values passed to zip(values, qualities) have to be qualified values already, which he doesn't have if he's passing values through a request body json, so he'd still have to be building the qvals. Derive would only help if he was reading the values with a system.tag.read* function, right ?

I thought you were receiving the values through a json ?

Well, for setting the quality to e.g. Uncertain I would like to keep the existing value. I do not want to send the values through the body because I don't know the values beforehand. I just want to set the quality and preserving the current value.

{
    "paths": [
        "[SolidRed]/Path/To/Device/MyString"
    ],
    "qualities": [
        "Uncertain"
    ]
}

So I tried getting the current values using system.tag.readBlocking and passing the output to system.tag.writeBlocking later on.

	from com.inductiveautomation.ignition.common.model.values import BasicQualifiedValue, QualityCode
	
	paths = request['data']['paths']	
	values = system.tag.readBlocking(paths)
	qualities = request['data']['qualities']	
	qvals = [qv.derive(getattr(QualityCode, quality, 'Uncertain')) for qv, quality in zip(values, qualities)]
	
    result = system.tag.writeBlocking(paths, qvals)
    
	return {'json': result}

But then I get the following error.

HTTP ERROR 501 Not Implemented

What if the quality is good ? Your original json did have values, I thought you'd be using those.

I'm not familiar with WebDev, but this looks like an issue with your endpoint. Is {'json': result} a proper response ?

In the example below, for the last update I only want to set the quality and keep the last value. I do not want to send over the value again (because I do not know it; I just want to invalidate the value by setting the quality).

Value   Quality     Message
1       Good        Path, Value
2       Good        Path, Value
4       Good        Path, Value
1       Good        Path, Value
1       Uncertain   Path, Quality

Yes, return {'json': result} is a valid response.

Similar script for creating tags is as follows:

	basePath = request['data']['basePath']
	tags = request['data']['tags']
	collisionPolicy = request['data']['collisionPolicy']

	result = system.tag.configure(basePath, tags, collisionPolicy)

	return {'json': result}

Where do the values come from for the first 4 rows then ?
It all seemed clear in the beginning and now I'm getting confused...

I can imagine. Sorry about that.

I have a couple of HTTP methods defined for CRUD of tags. See below the resource tag. This works as expected. This is what we talked about earlier. Like e.g. when I update a tag I provide the paths and values. When I read the tag I only provide the paths. And so on…

What I would like to do is to add a resource to update the tag quality. In the example below the resource is called setTagQuality. I believe that I only need to (and can) provide the paths and qualities.

Suppose a tag is updated every 30 seconds and the latest value is 4. If the tag is not updated for let’s say 60 seconds I want to set the quality to Uncertain and keep the latest value of 4. Note that I do not have this value ready anymore so I cannot send it in the body of the API call. It is stored in the tag value itself. So I think I need to retrieve it using system.tag.read* and afterwards write it using system.tag.write*.


setTagQuality

Update

def doPut(request, session):
  from com.inductiveautomation.ignition.common.model.values import BasicQualifiedValue, QualityCode
  paths = request['data']['paths']	
  values = system.tag.readBlocking(paths)
  qualities = request['data']['qualities']	
  qvals = [qv.derive(getattr(QualityCode, quality, 'Uncertain')) for qv, quality in zip(values, qualities)]	
  result = system.tag.writeBlocking(paths, qvals)  
  return {'json': result}

tag

Create

def doPost(request, session):
  basePath = request['data']['basePath']
  tags = request['data']['tags']
  collisionPolicy = request['data']['collisionPolicy']
  result = system.tag.configure(basePath, tags, collisionPolicy)
  return {'json': result}

Details about system.tag.configure can be found here.

Read

def doGet(request, session):
  values = system.tag.readBlocking(request['data']['paths'])
  return {'json': values}

Details about system.tag.readBlocking can be found here.

Update

def doPut(request, session):
  paths = request['data']['paths']
  values = request['data']['values']
  result = system.tag.writeBlocking(paths, values)
  return {'json': result}

Details about system.tag.writeBlocking can be found here.

Delete

def doDelete(request, session):
  paths = request['data']['paths']
  result = system.tag.deleteTags(paths)
  return {'json': result}

Details about system.tag.deleteTags can be found here.

Alright. So, indeed, you’ll have to read the values from the tags. The script you have there should work.
If sending a PUT request to your endpoint returns an error, I think it comes from your endpoint configuration.

I’m not sure if this actually works, but can you write a quality code directly to the .Quality property node?
That is - system.tag.writeBlocking(["path/to/tag.quality"], [qualityCode]). No need to read and derive in that case.