SmartCover Ignition API

Currently working on getting an API on Ignition to communicate with SmartCover - a data tracking package for manhole covers.
(Refer to this thread ---->SmartCover API with Ignition - #3 by nruimveld)

I see that there is Python code for an API with a token that would be applicable to Ignition. But I am not sure where or how I would insert this Python code on Ignition. Any insight? See below for code.

import requests
import csv
# variable to store the location id of interest
location_id = 'LOCATION_ID'
# variable to store the JWT token
token = 'TOKEN'
# set the authorization header
headers = {'Authorization': 'Bearer ' + token}
# set the request parameters
params = {
 'location': location_id,
 'start_time': '2020-07-01 00:00',
 'end_time': '2020-07-10 00:00',
 'data_type': '2' }
# make the GET request
response = requests.get('https://www.mysmartcover.com/api/locations/data.php',
params = params, headers = headers)
# store the response code
response_code = response.json()['response_code']
# store the data from the response
data = response.json()['data'][0]
# write the output to a CSV file
with open('output.csv', mode = 'w') as output_file:
 output_writer = csv.writer(output_file, delimiter = ',')
 output_writer.writerow(['datetime', 'level']) # write the column headers
 for row in data:
 output_writer.writerow(row)

In your case, importing the requests library will probably be unnecessary because of the built-in system.net.httpClient, system.net.httpGet and system.net.httpPost functions.

If you already have a token, you should be able to do something like this (off the top of my head):

client = system.net.httpClient()
url = "https://www.mysmartcover.com/api/locations/list.php"
headers = {
    "Authorization": "Bearer <YOUR_TOKEN_HERE>"
}
res = client.get(url, headers=headers)

if res.good:
    print res.json

The code should translate pretty exactly:

import csv
# variable to store the location id of interest
location_id = 'LOCATION_ID'
# variable to store the JWT token
token = 'TOKEN'
# set the authorization header
headers = {'Authorization': 'Bearer ' + token}
# set the request parameters
params = {
 'location': location_id,
 'start_time': '2020-07-01 00:00',
 'end_time': '2020-07-10 00:00',
 'data_type': '2' }
# make the GET request
response = system.net.httpClient().get('https://www.mysmartcover.com/api/locations/data.php', params = params, headers = headers)
# store the response code
response_code = response.json['response_code']
# store the data from the response
data = response.json['data'][0]
# write the output to a CSV file
with open('output.csv', mode = 'w') as output_file:
 output_writer = csv.writer(output_file, delimiter = ',')
 output_writer.writerow(['datetime', 'level']) # write the column headers
 for row in data:
 output_writer.writerow(row)

I should specify better; I don't quite understand where to implement this code onto Ignition. I'm unsure what setting, binding, etc. I need to put this code into.

It depends when you want to call it. You could put it in a gateway schedule event if you want it to run on a schedule

2 Likes

Do you actually want the output in a CSV file? I'd recommend a dataset tag, probably. Poll in a scheduled script as Nick recommends, build a dataset, write it to a tag. Then it's easy to pull into Vision or Perspective via a local binding.

1 Like

Pardon my delayed reply.

I think you have a great idea. However, I am struggling with defining the 'LOCATION_ID' variable in the Python script. There are some insights within the API documentation provided by SmartCover, however, I am having a hard time making sense of it. Any ideas to define/quantify location id within that Python script?

25014SD REV D API Documentation.pdf (373.7 KB)

You'll have to run their list method (manually, once) to determine the ID that corresponds to whatever unit(s) you're trying to retrieve historical data for; see page 5 where they're using curl (a commonly available tool to fetch from a URL via the command line).

I didn't think it was possible to use cURL on Windows, or within the schedule script. Maybe I am misunderstanding?
Is it possible to rewrite it in Python and add into the existing script?

I'm pretty sure Paul meant for you to write your own jython using the curl as an example. Use httpClient to do what curl is doing.

I see now, pardon me. I appreciate your guys' insights.

I seem to be getting an error: 'global name 'requests' is not defined'

I had assumed that the 'requests' library was already in Ignition's modules? Could I be overlooking something?

1 Like

Requests is not included in Ignition, and current versions are not compatible. Use Ignition's httpClient.

2 Likes

Ah! I see, that was mentioned earlier in the thread.

I've been trying to use the function in the line as below, but seem to keep getting a 'httpClient not defined' error. Is there another way I should be writing this?

response = httpClient.get('https://www.mysmartcover.com/api/locations/data.php', params=params, headers=headers)

The full name is system.net.httpClient(), provided by Ignition. It is typically instantiated/configured in a project library script into a top level variable, and that variable (often client) is then used for the actual API requests. Take a closer look at the sample (Paul's) above.

(And you can search this forum for "system.net.httpClient" for many more examples.)

2 Likes

For the task of fetching the list of locations, you can use literally any HTTP client. You could try one with a GUI like Postman, Insomnia, etc, or download cURL (it's definitely available for Windows, if not already on your system), or use one built in to a 'real' IDE like IntelliJ/PyCharm/VSCode/etc, or use the script console in Ignition. Hell, you could do it from your web browser console using Javascript and the fetch API.

The point is that HTTP/REST is pretty much a base lingua franca for communication between services and it doesn't matter what you have, most everything can talk to it. My read of the PDF you linked before suggests that SmartCover is doing something like this:
Each 'user' account has X meters associated with it. Once you get your (extremely long-lived...) token, when you ask the locations API, it's going to give you that list of X devices. Each of those have an ID, which is probably literally the same ID SmartCover is using on their own backend, and so you have to pass that ID into any cover-specific API method you want to call.

So the actual act of fetching that list is probably not something you'll have to repeat very often (only when you buy new covers?) - so it may not be worth writing up a script if you can pull the data you want out manually with a graphical tool. The actual process of talking to the cover, absolutely should be scripted, because you're going to be repeating that often. But a 'semi-automatic' process could be totally sufficient to get you past your current roadblock.

1 Like

Your response has done a lot to further my understanding in this project, and my understanding as a whole. I very much appreciate it.

I would like to circle back to your suggestion of putting into a dataset tag. Would would be the practice of doing so?

Datasets are an Ignition-specific construct that are designed to hold homogenously-typed columns of data with an arbitrary number of rows, associated with an identifier for the column name. You'll notice that this is basically the same as CSV, which is good for your purposes. :slight_smile:

So, your code sample from earlier:

Becomes something like:

headers = ["datetime", "level"]
dsData = []
for row in data:
	dsData.append([row['datetime'], row['level']])

dataset = system.dataset.toDataSet(headers, dsData)
system.tag.writeBlocking(["path/to/dataset/tag"], [dataset])

(Exact details will depend on the format of the data you're getting from the API, and you'll probably have to go through extra steps to parse whatever timestamp they're returning into an actual Java date object; see system.date.parse for one possibility.)

But that's the gist.

1 Like

Thanks again for your help with this task.