AirVantage REST API
In the previous post, I created a Java MQTT client to push data into the AirVantage cloud platform.
Next step is to read it out and make it available to OpenHAB. In this post I will talk about the AirVantage REST API, whereas in the next post I will provide details about my implementation of an OpenHAB binding to get data from AirVantage
Authentication
The first step in getting data is to authenticate yourself. AirVantage supports three kinds of authentication
For development, the easiest approach is to use the access token that can be read on the AirVantage portal.
For a real-world application, probably the most user-friendly approach is to define a username and a password in a configuration file. This is the approach I will use later to create OpenHAB binding. The URL to authenticate using a username-password pair, is as follow
"https://" + server + "/api/oauth/token?grant_type=password&username=" + username + "&password=" + password +
"&client_id=" + clientId + "&client_secret=" + clientSecret +
"&redirect_uri=oauth://airvantage"
server can be either na.airvantage.net or eu.airvantage.net
username and password are the credentials you typically use to login into the AirVantage portal
clientId and clientSecret identify the device that is requesting data and can be generated on the AirVantage.
The response includes the access token for use with following requests
{
"access_token": "fe47d528-7414-4962-a7e6-ee6b82491f7a",
"refresh_token": "9b465388-c9e2-45d3-98d0-1a44a503ec40",
"expires_in": 86400,
}
This URL returns an access token that needs to be appended to every URL. Note that access token has an expiration time. This means that care must be taken to refresh the access token periodically
Last value
To show the current (or better, the last) sensor reading in OpenHAB, the last known value has to be read, This can be accomplished by invoking the following URL
"https://" + server + "/api/V1/systems/" systemUid + /"data?access_token="
+ access_token + "&ids=" + data
systemUid is the unique identifier of the system to query. The Uid can be read on the portal
dataIds is a comma-separated list of data to read. Keeping in mind the AirMobile application, an example of dataIds is
dataIds=airmobile_temperature.value, airmobile_temperature.latitude, airmobile_temperature.longitude
The document that is returned has the following format
{
"airmobile_temperature.value": [{
"value": 30.1,
"timestamp": 1331906459440
}],
" airmobile_temperature.latitude ": [{
"value": 45.913405,
"timestamp": 1331906459440
}],
" airmobile_temperature.longitude": [{
"value": "10.998",
"timestamp": 1331906459440
}]
}
Raw values
Raw values are unfiltered values. Values are returned as sent by the field devices. Only values for the last three hours or 10000 values are available as raw data.
To get raw values, the following URL needs to be invoked
"https://" + server + "/api/V1/systems/data/raw?access_token="
+ access_token + "&targetIds=" + systemUid + "&dataIds=" + data
The response to this request has the following format
{
"4c6cfc5407ad4a7985e3792408bbdb4e":
{
"airmobile_temperature.value": [{
"value": 30.1,
"timestamp": 1331906459440
}],
" airmobile_temperature.latitude ": [{
"value": 45.913405,
"timestamp": 1331906459440
}],
" airmobile_temperature.longitude": [{
"value": "10.998",
"timestamp": 1331906459440
}]
}
}
Aggregate values
If data over a longer time interval is required, data must be accessed using aggregate functions. Aggregate functions apply a certain function over data in a given time interval (for example one hour) and return that value instead of all the value in that time interval. Available aggregate functions include mean, sum, min, max, standard deviation, number of samples
To get aggregate values, the following URL needs to be invoked
"https://" + server + "/api/V1/systems/data/aggregated?access_token="
+ access_token + "&dataIds=" + data
+ "&targetIds=" + systemUid
+ "&from=" + (System.currentTimeMillis() - 23 * 60 * 60 * 1000)
+ "&to=" + (System.currentTimeMillis() + 1 * 60 * 60 * 1000)
There are other possible parameters that can be added to the URL, like interval. Intervals are specified by a multiplier and a unit of time, i.e. 6hour or 1day. The supported time units are:
- hour - Hours
- day - Days
- month - Months
- year - Years
The multiplier has to be lower than 50. The default interval is 1 hour
The response to this request is similar to the response shown in previous section
An helper class
All this methods have been encapsulated in a Java class (AirVantageClient.java) that will be very useful in the next post, when I will talk about the OpenHAB binding implementation
This class has a few fetaures that worth to be highlighted
Deserialization
I used GSON library to deserialize responses. Because responses may contain fields whose name has periods in it, a custom deserializer is required. This is for example the implementation of the custom deserializer for raw data response
publicclass AirVantageRawData { public List<Datapoint> values; public List<Datapoint> lats; public List<Datapoint> longs; }; private class AirVantageRawDataDeserializer implements JsonDeserializer<AirVantageRawData> { @Override public AirVantageRawData deserialize(JsonElement json, Type type, JsonDeserializationContext context) throws JsonParseException { JsonObject jobject = (JsonObject) json; AirVantageRawData data = new AirVantageRawData(); Type collectionType = new TypeToken<List<Datapoint>>() {}.getType(); JsonArray array = jobject.get("airmobile_temperature.value").getAsJsonArray(); data.values = context.deserialize(array, collectionType); array = jobject.get("airmobile_temperature.latitude").getAsJsonArray(); data.lats = context.deserialize(array, collectionType); array = jobject.get("airmobile_temperature.longitude").getAsJsonArray(); data.longs = context.deserialize(array, collectionType); return data; } }
The usage of the deserializer is as follow
Gson gson = new GsonBuilder() .registerTypeAdapter(AirVantageRawData.class, new AirVantageRawDataDeserializer()) .create(); AirVantageRawData rawData = gson.fromJson(isr, collectionType);
Data fusion
Unfortunately it is not possible to read out a complete asset. Instead, it is only possible to read a specific data value in an asset.
For this reason, I created a function to merge the three array of data values (value, latitude and longitude) into a single class, because having a single class is definitely more handy. This is the code to merge data value arrays
publicclass GeoDatapoint { public long timestamp; public double latitude; public double longitude; public double value; } List<GeoDatapoint> points = new ArrayList<GeoDatapoint>(); for (int i=0; i<rawData.values.size(); i++) { Datapoint lat = rawData.lats.get(i); Datapoint lon = rawData.longs.get(i); Datapoint val = rawData.values.get(i); GeoDatapoint dp = new GeoDatapoint(); dp.latitude = lat.value; dp.longitude = lon.value; dp.value = val.value; points.add(dp); }