AirNow Air Quality JSON parsing

My Item:

String  AirNow_status          "AirNow JSON"                  {http="<[http://www.airnowapi.org/aq/observation/zipCode/current/?format=application/json&zipCode=25404&distance=5&API_KEY=50358C98-A552-4CE3-BD34-94CCFD285FBA:60000:REGEX((.*)
)]"}

Gives back the folowing JSON:

[{"DateObserved":"2016-10-08 ","HourObserved":13,"LocalTimeZone":"EST","ReportingArea":"Martinsburg","StateCode":"WV","Latitude":39.4656,"Longitude":-77.9583,"ParameterName":"O3","AQI":17,"Category":{"Number":1,"Name":"Good"}}]

My Rule:

rule "AirNow JSON parsing"
when
  Item AirNow_status changed
then
  var String json = AirNow_status.state.toString
  var Number aqi = new Integer(transform("JSONPATH", "$.AQI", json))
  logInfo("AirNow","AQI: " + aqi)
end

The problem is its not capturing just the AQI value.

2016-10-08 15:25:19.977 [ERROR] [o.o.c.s.ScriptExecutionThread ] - Error during the execution of rule 'AirNow JSON parsing': For input string: "[{"DateObserved":"2016-10-08 ","HourObserved":14,"LocalTimeZone":"EST","ReportingArea":"Martinsburg","StateCode":"WV","Latitude":39.4656,"Longitude":-77.9583,"ParameterName":"O3","AQI":18,"Category":{"Number":1,"Name":"Good"}}]"

It looks like you are getting back a JSON array containing one element, so I think you would need "$[0].AQI" .

Hi,

there are also some nice online resources to validate & experiment with JSON parsing - e.g.:

with kind regards,
Patrik

1 Like

Links now added to the Transformation Wiki

I just signed up and tested this out and it appears to be working:

rule "Parse Air Quality"
when
        Item Weather_Air_Quality_Report received update
then
        val aqi = transform("JSONPATH", "$[0].AQI", Weather_Air_Quality_Report.state.toString)
        val cat = transform("JSONPATH", "$[0].Category.Name", Weather_Air_Quality_Report.state.toString).replace('\"', '')
        logInfo(logNameWeather, "Received air quality update: AQI = " + aqi + " Category = " + cat)

        Weather_AQI.sendCommand(aqi)
        Weather_Air_Quality_Category.sendCommand(cat)
end

You can also grab the AQI directly in the Item definition without the Rule. My understanding is that the HTTP binding caches the pulled down data so you can populate multiple Items from one request.

However a straight JSONPATH transform will keep the quotes around the Category (hence the replace above). You can get around this using a JavaScript transform.

(function(i) {
   var data = JSON.parse(input)
   return data[0].Category.Name
})(input)
// input variable contains data passed by openHAB

This seems to strip the quotes off nicely.

Number Weather_AQI "Air Quality Index [%d]" { http="<[airqual:300000:JSONPATH($[0].AQI)]"}
Number Weather_Air_Quality_Category "Air Qualitiy [%s]" { http="<[airQual:300000:JS(airqualCat.js)]" }

Of course, good old XSLT will work as well if you pull down the XML values instead of JSON.

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output indent="yes" method="xml" encoding="UTF-8" omit-xml-declaration="yes" />
        <xsl:template match="/">
                <xsl:value-of select="//ObsByLatLonList/ObsByLatLon/AQI"/>
        </xsl:template>
</xsl:stylesheet>
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
        <xsl:output indent="yes" method="xml" encoding="UTF-8" omit-xml-declaration="yes" />
        <xsl:template match="/">
                <xsl:value-of select="//ObsByLatLonList/ObsByLatLon/CategoryName"/>
        </xsl:template>
</xsl:stylesheet>
Number Weather_AQI "Air Quality Index [%d]" { http="<[airqual:300000:XSLT(airQualAQI.xsl)]"}
Number Weather_Air_Quality_Category "Air Qualitiy [%s]" { http="<[airQual:300000:XSLT(airQualCat.xsl)]" }

I’m trying to get smart on transforms…

1 Like

Great examples, Rich. I would only suggest you use postUpdate instead of sendCommand, since you’re not commanding the air to have a certain quality (unless you have some magical IoT device :slight_smile: ).

Muahahahahaha! :smiley:

Good point, postUpdate is the most appropriate here. Out of curiosity, in this particular case are the two functionally equivalent since the Items have nothing bound to them, or is there something additional that takes place when using sendCommand that one would want to avoid in this case. I can see some specific cases where one would deliberately want to choose one or the other but in the generic case like this one I wonder if there is actually a difference in how they are processed internally.

My only point is that postUpdate is just to update the current state of an item, while sendCommand is meant to tell the far end system to do something. sendCommand has the side effect of also setting the item’s state to the command, unless autoupdate logic suppresses this side effect. So it’s really just about removing one small potential bit of confusion about commands vs. states. Thanks again for sharing your examples!

@rlkoshak

Rich,

Sorry to bump an old thread but I’ve tried to replicate your work but I keep getting the same error show up. Can you help me out please?

The JSON Response:

{
"status":"success",
"time":0.01,
"flags":{
},
"data":{
"device_type":"light",
"id":42349,
"label":"Ceiling Light",
"device_id":5164,
"power_state":0,
"startup_mode":0,
"aggregated_hourly_at":"2017-01-14T12:05:59.000Z",
"aggregated_daily_at":"2017-01-14T12:05:59.000Z",
"remote_id":1,
"timer1_enabled":null,
"timer1_on_time":null,
"timer1_off_time":null,
"timer1_monday":null,
"timer1_tuesday":null,
"timer1_wednesday":null,
"timer1_thursday":null,
"timer1_friday":null,
"timer1_saturday":null,
"timer1_sunday":null,
"timer2_enabled":null,
"timer2_on_time":null,
"timer2_off_time":null,
"timer2_monday":null,
"timer2_tuesday":null,
"timer2_wednesday":null,
"timer2_thursday":null,
"timer2_friday":null,
"timer2_saturday":null,
"timer2_sunday":null,
"timer3_enabled":null,
"timer3_on_time":null,
"timer3_off_time":null,
"timer3_monday":null,
"timer3_tuesday":null,
"timer3_wednesday":null,
"timer3_thursday":null,
"timer3_friday":null,
"timer3_saturday":null,
"timer3_sunday":null,
"extra_data":null,
"target_temperature":null,
"voltage":null,
"voltage_reported_at":null,
"frequency":null,
"real_power":null,
"reactive_power":null,
"created_at":"2017-01-14T12:05:59.000Z",
"updated_at":"2017-01-14T12:15:07.000Z",
"nest_thermostat_id":null,
"rate_limit_tokens_used":10,
"rate_limit_tokens_updated_at":"2017-04-14T18:59:23.000Z",
"device_groups":[
{
"id":38337,
"name":"Nathan \u0026 Jess Room",
"user_id":30732
}
],
"today_wh":0,
"today_wh_range":"low",
"last_data_instant":0,
"unknown_state?":true,
"unknown_state":true
}
}

The Rule:

rule "Parse Json"
when
        Item PowerStateBedroomLight_Output received update
then
        val power = transform("JSONPATH", "$[0].power_state", PowerStateBedroomLight_Output.state.toString)
        logInfo("Parse Json result = " + power)
        powerState.postUpdate(power)
end

The Error:

2017-04-18 11:55:48.931 [ERROR] [ore.transform.actions.Transformation] - Error executing the transformation 'JSONPATH': An error occured while transforming JSON expression.
2017-04-18 11:55:48.937 [ERROR] [.script.engine.ScriptExecutionThread] - Rule 'Parse Json': An error occured during the script execution: index=1, size=1 

If there is anything else you need please let me know.

Thanks

logInfo takes two arguments, a log name and the log message.

logInfo("JSON", "Parse Json result = " + power)
1 Like

Hi,

So I updated the rule so it has two arguments:

rule "Parse Json"
when
        Item PowerStateBedroomLight_Output changed
then
        val power = transform("JSONPATH", "$[0].power_state", PowerStateBedroomLight_Output.state.toString)
        logInfo("Testing", "Parse Json result = " + power)
        powerState.postUpdate(power)
end

Now I get the following error:

2017-04-19 18:11:43.878 [ERROR] [ore.transform.actions.Transformation] - Error executing the transformation 'JSONPATH': An error occured while transforming JSON expression.

Followed by the exact copy of the command output. I thought it could be to do with $[0]. Of the code so tried changing the 0 to 1 then 2 through to 4 but no luck.

I don’t know much about JSONPATH. All I know is all my successful examples look something like:

JSONPATH($.forecast.simpleforecast.forecastday[1].snow_allday.in)]

Note that it starts with just a $.

From looking at your JSON I don’t think the [0] is even required.

If this doesn’t help you will need to look into JSON and JSONPATH more. I won’t be much help.

1 Like

Finally worked it out!

val power = transform(“JSONPATH”, “$[0].power_state”, PowerStateBedroomLight_Output.state.toString)

Removed the [0] as you suggested and changed the detail as the word “data” was missing from the path response:

val power = transform(“JSONPATH”, “$data.power_state”, PowerStateBedroomLight_Output.state.toString)

I apologize for how untimely this reply is (and it looks like plenty of other solutions have been found), but I was dealing with the same exact AQI parsing issue the original poster had and found a simpler solution (IMHO) that only uses the item binding. I hope this saves other people some trouble, since this is the first thread that popped up when I looked for help.

The HTTP binding has a transformation built into it:
http="<[<url>:<refreshintervalinmilliseconds>:<transformationrule>]"

Since I don’t really know anything about anything, especially transformations, I stuck with regex (which seems relatively straightforward and has great online tools available, as others have mentioned). From the same JSON response I have created 4 different items, each with its own HTTP request (perhaps not great if you have limited API access):
1 - ozone (“O3”) AQI (number)
2 - particle (“PM2.5”) AQI (number)
3 - ozone description (string, e.g. “Good”), and
4 - particle description (string).

I don’t see the PM2.5 (particulate matter) values in the original posts, but my JSON response includes them. I have included them as well since they are usually more helpful than ozone values when talking about air quality where I live due to the wildfires.

Here are the corresponding regex transformations from my item bindings (after “:60000:” and before “]”)
1 - REGEX(.*((?<=3...AQI..)[0-9]+).*)
2 - REGEX(.*((?<=5...AQI..)[0-9]+).*)
3 - REGEX(.*([A-Z].+(?=......D)).*)
4 - REGEX(.*([A-Z].+(?=...])).*)

In the first two I’ve used positive lookbehinds and in the second two I’ve used positive lookaheads to make sure I grabbed the correct values from the following JSON:

[{"DateObserved":"2018-01-26 ","HourObserved":14,"LocalTimeZone":"PST","ReportingArea":"San Francisco","StateCode":"CA","Latitude":37.75,"Longitude":-122.43,"ParameterName":"O3","AQI":27,"Category":{"Number":1,"Name":"Good"}},{"DateObserved":"2018-01-26 ","HourObserved":14,"LocalTimeZone":"PST","ReportingArea":"San Francisco","StateCode":"CA","Latitude":37.75,"Longitude":-122.43,"ParameterName":"PM2.5","AQI":46,"Category":{"Number":1,"Name":"Good"}}]

1 - Ozone AQI: 27
2 - Particle AQI: 46
3 - Ozone description: Good
4 - Particle description: Good

I’m sure there are more elegant ways to do the regex transformations, but the JSON response is consistent enough that I haven’t had any problems yet with the lookarounds. This should be easy to adapt to other situations involving the HTTP binding, so I hope this was helpful.