Community Tip - Stay updated on what is happening on the PTC Community by subscribing to PTC Community Announcements. X
The Axeda Platform has long had the ability to write custom logic to retrieve, manipulate and create data. In the current versions of the Platform, there are two classes of API, Version 1 (v1) and Version 2 (v2). The v1 APIs allow a developer to work with data on the Platform, but all of the APIs are subject to the maxQueryResults configuration property, which by default limits the number of results per query to 1000. For some subsets of data, this can be inadequate to process data. In comes the v2 API, which introduces pagination.
One of the first things a new user does when exploring the V2 API, is something like the following:
HistoricalDataItemValueCriteria criteria = new HistoricalDataItemValueCriteria()
criteria.assetId = '9701'
criteria.startDate = '2014-07-23T12:33:00Z'
criteria.endDate = '2014-07-23T12:44:00Z'
DataItemBridge dbridge = com.axeda.sdk.v2.dsl.Bridges.dataItemBridge
FindDataItemValueResult results = dbridge.findHistoricalValues(criteria)
And they get frustrated when they only get the same 100 rows of data. Repeat after me: V2 API invocations (find operations) are limited to batches of 100 results at a time!
But that's not the end of the story. With a small change, the query above can be tuned to iterate through all results that match the search criteria:
HistoricalDataItemValueCriteria criteria = new HistoricalDataItemValueCriteria()
criteria.assetId = '9701'
criteria.startDate = '2014-07-23T12:33:00Z'
criteria.endDate = '2014-07-23T12:44:00Z'
criteria.pageNumber = 1
criteria.pageSize = 100 // Default.
DataItemBridge dbridge = com.axeda.sdk.v2.dsl.Bridges.dataItemBridge
FindDataItemValueResult results = dbridge.findHistoricalValues(criteria)
tcount = 0
while ( (results = dbridge.findHistoricalValues(criteria)) != null && tcount < results .totalCount) {
results.dataItems.each { res ->
tcount++
}
criteria.pageNumber = criteria.pageNumber + 1
}
I currently recommend that people avoid using the count() or countDomainObjectByCriteria() functions if you're then going to call a find. Currently both the count*() and find functions compute total results, and doubles execution time of just those two calls. Total count is only computed when running the first find() operation, so the code pattern above is so far the most efficient way I've seen to run these operations on the platform.
So having covered how to do this in code (custom objects), let's turn our attention to the REST APIs - the other entry-point for using these capabilities. The REST API doesn't offer a count*() function, but the first find() invocation (if using XML) brings back totalCount as part of the result set. You can use this in your application to decide how many times to call the REST end-point to retrieve your data. So for the example above:
POST: https://customer-sandbox.axeda.com/services/v2/dataItem/findHistoricalValues
HEADERS:
Content-Type: application/xml
Accept: application/xml
BODY:
<?xml version="1.0" encoding="UTF-8"?>
<HistoricalDataItemValueCriteria xmlns="http://www.axeda.com/services/v2" pageSize="100" pageNumber="1">
<assetId>9701</assetId>
<StartDate>2014-07-23T12:33:00Z</StartDate>
<endDate>2014-07-23T12:35:02Z</endDate>
</HistoricalDataItemValueCriteria>
RESULTS:
<v2:FindAssetResult totalCount="1882" xmlns:v2="http://www.axeda.com/services/v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<v2:criteria pageSize="100" pageNumber="1">
<v2:name>*</v2:name>
<v2:propertyNames/>
</v2:criteria>
<v2:assets>
</v2:assets>
</v2:FindAssetResult>
Or JSON:
POST: https://customer-sandbox.axeda.com/services/v2/dataItem/findHistoricalValues
HEADERS:
Content-Type: application/xml
Accept: application/xml
BODY:
{
"id": 9701,
"startDate": "2014-07-23T12:33:00Z",
"endDate": "2014-07-23T12:35:02Z",
"pageNumber": 1,
"pageSize": 2
}
And that's how you work around the maxQueryResults limitation of the v1 APIs. Some APIs do not currently have matching v2 Bridges (e.g. MobileLocation and DataItemAssociation), in which case the limitation will still apply. Creative use of the query Criteria will allow you to work around these limitations as we continue to improve the V2 API.
Regards,
-Chris
Thanks Chris.
One small correction. I think you meant to write "criteria.pageNumber ++" in the below block:
while ( (results = dbridge.findHistoricalValues(criteria)) != null && tcount < results .totalCount) {
results.each { res ->
tcount++
}
results.pageNumber++
}
Also, for some use cases, it would be a performance gain to be able to increase the page size. Food for thought
Thank you - I have made that correction.
-Chris Kaminski
PTC Customer Support
Chris,
I used your example and can list the historical data, see code below, but I cannot figure out how to show the date for each of the data values.
Can you show me how to retrieve the date for each historical data value?
Thanks,
John
import static com.axeda.sdk.v2.dsl.Bridges.*
import com.axeda.services.v2.*
def asset = assetBridge.find("590ND32DNZA2MNZA2B71||916512000219")
assert asset
logger.info("found asset");
def findResult = dataItemBridge.findCurrentValues(
new CurrentDataItemValueCriteria(
assetId: asset.systemId,
//name: "* level",
types: [DataItemType.ANALOG]))
findResult.dataItemValues.each { logger.info("Current Data: ${it.dataItem.label}: ${it.value}") }
HistoricalDataItemValueCriteria criteria = new HistoricalDataItemValueCriteria()
criteria.assetId = asset.systemId;
criteria.startDate = new Date() - 365;
criteria.endDate = new Date();
criteria.pageNumber = 1
criteria.pageSize = 100 // Default.
FindDataItemValueResult findHistResult = dataItemBridge.findHistoricalValues(criteria);
tcount = 0
while ( (findHistResult = dataItemBridge.findHistoricalValues(criteria)) != null && tcount < findHistResult.totalCount)
{
findHistResult.dataItemValues.each {
tcount++;
logger.info("Historical Data: ${it.dataItem.label}: ${it.value}");
}
criteria.pageNumber = criteria.pageNumber + 1
}
logger.info("Historical Data: ${it.dataItem.label}: ${it.value}");
should become:
logger.info("Historical Data: ${it.dataItem.label}: ${it.value} : ${it.timestamp} ");
I'm not sure how familiar with Groovy closures you are, but 'it' is a special alias. You can change this:
findHistResult.dataItemValues.each {
to this
findHistResult.dataItemValues.each { dataItemValue ->
If it helps (or some other name). If you don't give one, Groovy uses 'it' as a default.
So dataItemValue.dataItem.label is the name of the data item, dataItemValue.value is it's current value, and dataItemValue.timestamp is the time in milliseconds from EPOCH (in GMT).