How To? Push Perspection root custom property value to tag

Hey all,
I am polling data from wireless sensors using an API Get. This data is stored as a custom property in my perspective root. What is the best way to "push" that data value to a tag for historization?

Note, I am a PLC programmer and learning my way around SCADA, so ELI5 please.

Thanks

This seems kind of backwards. Write the sensor API call to their respective tags, then bind the custom props to the tag.

result = script.apiCall()
tagWritePaths = ["[default]Plant5/Sensor1"]
system.tag.writeBlocking(tagWritePaths)

These pages are very handy while learning what Ignition can do.

For scripts
System Functions - Ignition User Manual 8.1 - Ignition Documentation (inductiveautomation.com)

Expression language
Expression Functions - Ignition User Manual 8.1 - Ignition Documentation (inductiveautomation.com)

References
Reference Pages - Ignition User Manual 8.1 - Ignition Documentation (inductiveautomation.com)

Hi @Josh_Gale and welcome to the forum.

Like @dkhayes117 have mention is better to get the data to tag and then bind them to the perspective. This would also be easier to activate history and alarming on it if wanted.

I've done this way on one of my exchange ressource.

from the API call I get a json string and I use derived tags to extract the wanted value from that string.
the tag where I do the call is then refreshing at a given rate.

I can't just not remember is I've already done this way on the first version of the ressource or on the second one.

Anyway if you need some help don't hesitate. I'm just in Belgium so it my be some delay in my answer depending where you are :slight_smile:

regards

Thanks guys, dkhayes117, De_Clerck_Arnaud. (Board won't let me mention yet)

I used a http binding on the custom prop to pull the data from the API. I have been running in circles in an attempt to figure this out... event timers with script, the weather project, 30 tabs open on google. I think I am just into a "I don't know what I don't know" situation.

Is there a way to set a http binding to a tag the same way you can a property? Again, I am a PLC programmer so I feel like there is a base layer of knowledge to use the more advanced side of Ignition that I am lacking.

Thanks again

I don't think it's a direct way to do it.

can you talk more about the setup you have, witch sensor are you using the way you try to connect to it (direct via a gateway ?)

regards

There's no super convenient way to set up an HTTP binding on a tag, unfortunately.

Probably the easiest way to go would be:

  1. Create a project that you will designate as your 'Gateway Scripting Project' (in the gateway settings)
  2. Create a project script with a function that will perform your HTTP request (highly recommend system.net.httpClient() here), then return it. Remember the name of the project script and function.
  3. Create an expression tag of type Document that uses the runScript expression function to...run your project script. As long as you set the gateway scripting project, you just provide the name of your project script and function (myScript.myFunction) as the first argument, and the poll rate (in milliseconds) as the second argument.
  4. Create derived tags to retrieve individual values from the JSON document response on the tag.

Thank you both again,

I am struggling getting the syntax correct for passing headers with the system.net.httpget and system.net.httpclient.

For the binding setup I have
"URL"
Headers
accept "application/json"
ApiKey "apikey"

The error I am seeing in the Gateway Script Log is "not all arguments converted during string formatting"

Edit: PGriffith, is there a way to see what is being generated by the HTTP binding?

postData = {
					"code":"request",
					"cid":-1,
					"adr":"/iolinkmaster/port[%s]/iolinkdevice/pdin/getdata" % xport
				}
				postData = system.util.jsonEncode(postData)
				res = system.net.httpPost(url=url, contentType="application/json", postData=postData)
				res = system.util.jsonDecode(res)
				dataValue = res["data"]["value"]

that's and example of how it's made for a ifm sensor

1 Like

The equivalent snippet with httpClient, which takes care of the content type and encoding/decoding for you:

postData = {
	"code":"request",
	"cid":-1,
	"adr":"/iolinkmaster/port[%s]/iolinkdevice/pdin/getdata" % xport
}

client = system.net.httpClient()
res = client.get(url, data=postData)
json = res.json
dataValue = json["data"]["value"]

1 Like

I have tried both sets of code using httpClient and httpGet and decode. I am getting an 401 error from the httpGet and a malformed json body error with the httpClient.

Let me know if you see any issues.

headers = {
"accept" : "application/json",
"ApiKey" : "apikey..."
}

headers = system.util.jsonEncode(headers)
returnValue = system.net.httpGet(url="https://aranet.cloud/api/v1/measurements/last?links=false", data=headers)
returnValue = system.util.jsonDecode(returnValue)
sensorData = returnValue["data"]["value"]

and

headers = {
"accept" : "application/json",
"ApiKey" : "apikey..."
}

client = system.net.httpClient()
returnValue = client.get(url="https://aranet.cloud/api/v1/measurements/last?links=false", data=headers)
json = returnValue.json
sensorData = json["data"]["value"]

Headers are not the same as data. Pass your headers as headers (you shouldn't need the accept header, just the API key).
I would try this:

headers = {
	"ApiKey" : "apikey..."
}

params = {
	"links": "false"
}

client = system.net.httpClient()
returnValue = client.get(url="https://aranet.cloud/api/v1/measurements/last", params=params, headers=headers)
json = returnValue.json
sensorData = json["data"]["value"]

Thank you, I am now communicating and able to view the data in the script console if I print the json variable, but there is a KeyError with 'data'. Is this because there are multiple sensors being polled? ie. This call is for multiple json objects?

It depends on how your data is structured. At the top level there's typically some key; it could be named data or it could be named just about anything else. If you're able to copy the JSON output here, we could give you some guidance; otherwise you'll probably just have to change the key names until you get something working.

Got it,

When I use the http binding the structure looks like this.

SensorData{1}
-readings[67]
--0{6}
---metric : 1
---sensor : 6292422
---unit : 101
---time : time...
---value : 67.8
---novelty: new
etc... for the rest of the readings array.

Snippet of the raw output from the script console below.

{'readings': [{'novelty': u'new', 'unit': u'101', 'metric': u'1', 'sensor': u'6292422', 'time': u'2022-10-07T16:57:05Z', 'value': 72.7}, {'novelty': u'new', 'unit': u'115', 'metric': u'8', 'sensor': u'6292422', 'time': u'2022-10-07T16:57:05Z', 'value': -10.9}, {'novelty': u'new', 'unit': u'130', 'metric': u'9', 'sensor': u'6292422', 'time': u'2022-10-07T16:57:05Z', 'value': 1.6},...

Edit:
Thank you all, I am able to process the data using sensorData = json["readings"]. I have been able to loop through and write to variables. I will post my finalized code when I am done.

3 Likes

I'm really glad you found your way to it. I know it can be quite difficult if you don't have the knowledge to implement something like this.

I would suggest to maybe create a exchange ressource when you are done so if anyone use the same sensor as you it will be a simple download for him.

regards

@PGriffith, what would cause this to run just fine in the Script Console but not in a periodic Gateway Script? I also tried to create a local script and calling it from the Gateway, and from a button press for testing. The Gateway call showed a success, but I believe it was showing a call success, not a success in the script.

--Script that Fails--

headers = {
"ApiKey" : "xxx"
}

client = system.net.httpClient()
returnValue = client.get(url="https://aranet.cloud/api/v1/measurements/last?links=false", headers=headers)
json = returnValue.json
sensorData = json["readings"]

--Error Details for the Button Script Test--

com.inductiveautomation.ignition.common.script.JythonExecException
Traceback (most recent call last):
File "function:runAction", line 9, in runAction
java.io.IOException: java.io.IOException: Unable to GET https://aranet.cloud/api/v1/measurements/last?links=false

caused by org.python.core.PyException

Traceback (most recent call last):
File "function:runAction", line 9, in runAction
java.io.IOException: java.io.IOException: Unable to GET https://aranet.cloud/api/v1/measurements/last?links=false

caused by IOException: Unable to GET https://aranet.cloud/api/v1/measurements/last?links=false
caused by ConnectException: Cannot assign requested address
caused by ConnectException: Cannot assign requested address
caused by BindException: Cannot assign requested address

Ignition v(Dev Version)
Java: Azul Systems, Inc. 11.0.11

I'll be honest, I've never seen that before. From some googling, it seems to be a DNS or other resolution error; or possibly it's port contention on your gateway server?

Also, in an abundance of caution I edited out the API key you posted; not sure how sensitive that might be for you.

Thank you for editing the key, I pasted from the wrong clipboard.

Is there any direction I should be looking for a resolution? Is there another API Get route I can take?

Can you, outside of Ignition, reach that URL from the gateway server? E.G. in a web browser, or if it's a headless system, via something like curl or wget? Just to see if it's even able to resolve, not necessarily trying to make a proper HTTP request.

I've been working with my IT guy. It appears the API call is not closing the communication and it's filling the TCP stack. We have tried adding a timeout, but is there a way to force close the communication after the Get function?