Rules and Expressions with Open Weather API

Can someone tell me what I have wrong in these 2 expressions querying the OpenWeather API?
They are string variables and return <>

This is the what I use for wind speed but I use the manual method.


{{round($context.response.data.current.wind_speed)}} mph

It will return a string like this

3 mph

1 Like

That worked for wind speed. Still getting an error for wind gust. I reformatted gust to what you are using.

This should work.

{{round($context.response.data.current.wind_gust)}} mph

I have got this … !! Icons are animated gifs made by myself …

6 Likes

I’m using the API call to pull in this information and it works just fine. To pull it in, mine looks like this:


and the output looks like this:
image

My questions are about the json response:

  • Since the ‘alert’ section won’t always be included in the response, is there an appropriate way to process it only when data exists (maybe an if statement to check to see if it exists before processing it).
  • Since I wouldn’t know how many values for the ‘alert’ exist (today it is both wind and tornado) – how should I handle this to only process the correct number of ‘events’ that exist in the json file?
  • Is there any way to insert a carriage return into the text?

If it’s an array, using the map() concept below accounts for this.

Otherwise, using a conditional ternary expression works:

condition ? trueOperation : falseOperation

Since it’s an array, I would probably use a map() and join() to handle it.

alerts = $context.response.data.alerts
myString = map(alerts, concat(x.event, " until ", formatDate(x.end * 1000, "h:mm a")))
join(myString, "\r\n")

The General Functions in Expressions documentation covers the basics above the above functions or you can search the community as there were a few recent posts about expressions and arrays that may be helpful. This discussion links to a few Array related discussions of interest.

The \r\n in my example above inserts a new line (Carriage Return / Line Feed). It won’t show up in the variable preview or variable editor, but it will in a notification or a dashboard variable tile.

Thanks…

I’m missing something yet… Could you please share your API pull statement?

You can get it from the below, there are several topics on opening an account, getting a key and calling the OpenWeatherMap API.

josh

Aug '22

You can do this with the HTTP Action in SharpTools today. I agree that it makes for a good feature request as a native integration though to simplify this process for people who don’t have weather devices attached to their SmartThings. :smiley:

Let’s take the Open Weather /weather endpoint which provides the current weather. The URL is in the format:

https://api.openweathermap.org/data/2.5/weather?lat=-33&lon=96&appid={{ApiKey}}

And the response is in the format:

{
   "coord":{
      "lon":96,
      "lat":-33
   },
   "weather":[
      {
         "id":500,
         "main":"Rain",
         "description":"light rain",
         "icon":"10n"
      }
   ],
   "base":"stations",
   "main":{
      "temp":286.4,
      "feels_like":286.08,
      "temp_min":286.4,
      "temp_max":286.4,
      "pressure":1032,
      "humidity":88,
      "sea_level":1032,
      "grnd_level":1032
   },
   "visibility":10000,
   "wind":{
      "speed":6.89,
      "deg":143,
      "gust":7.83
   },
   "rain":{
      "1h":0.19
   },
   "clouds":{
      "all":97
   },
   "dt":1661023588,
   "sys":{
      "sunrise":1661040462,
      "sunset":1661080271
   },
   "timezone":21600,
   "id":0,
   "name":"",
   "cod":200
}

So if we wanted to get the main weather report, we would be looking for the weather.0.main element in the response. So we can make the HTTP call and then get the response data using Context Variables (Response > HTTP > Data) and use the object property notation to grab that nested property for use in our notification:

1 Like

ok, I had a OpenWeather 3.0 API and it seems to be able to pull the response. I appear to have an issue with the CONCAT statement: << expression evaluation error >>

Couple of things:

  • The one I’m using is the 3.0 version as well:
  • If you don’t have a weather update today (hopefully you don’t) - you’ll get the ‘<< expression evaluation error >>’ – but you can check by pasting your openweather API link into a browser, copying that response and then pasting into a json formatter. It’ll look something like this when you have an alert (this was mine from yesterday):
    image

What I can’t figure out is how to set a boolean variable to true if the alert exists and then set the alert field, and if its false to set to plain language such as ‘No Weather Alerts Today’

I keep trying something like this, but no luck as the $context.response.data.alerts doesn’t exist in the json

image

However, Josh’s code above is a much better path of going about this.

1 Like

Hi @Jason_K_Jennings and @dave.blackwell - I hope you don’t mind that I moved your posts out of the Open Weather Custom Tile thread and into this Open Weather Rule thread. Great discussion above!

If the ‘alerts’ field exists, but it might be an empty array, you could do something like the following:

weather = $context.response.data
count(weather.alerts) > 0

If you’re not sure if the field will exist or not, you could either:

  • Initialize an expression scoped variable with an empty array if the alerts doesn’t exist
  • Conditionally evaluate the count or not

Initialize Array

weather = $context.response.data
alerts = isEmpty(weather.alerts) ? [] : weather.alerts
count(alerts) > 0

In the second line, we’re using isEmpty() which can check for an empty string, empty array, or in this case a missing property (eg. undefined). And even if the property did exist and was empty, this doesn’t hurt as it’s still just using an empty array in that case. And if it does exist and does have content, it uses the content.

Conditionally Count

weather = $context.response.data
isEmpty(weather.alerts) ? false : count(weather.alerts) > 0

Same concept as above with using isEmpty(). This ones a bit shorter, but I personally find the logic of the other one easier to interpret… which is important when I try to edit an expression a few months later and need to remember what the heck I did!

1 Like

I would add that you can combine that all together into a single expression to build your string or use a fallback string.

You can even do it with expression-scoped variables so you don’t have to have a bunch of other global variables if you don’t need them.

alerts = $context.response.data.alerts

hasAlerts = count(alerts) > 0

myString = map(alerts, concat(x.event, " until ", formatDate(x.end * 1000, "h:mm a")))
join(myString, "\r\n")

hasAlerts ? myString : "No alerts"

So from top to bottom:

  1. Alias the alerts data with a simpler (expression-scoped) variable
  2. Check if we even have alerts
  3. Build the alert string to format the various (potential) events
  4. If we have events, show the string, otherwise show a default message
2 Likes

Wow, this is good!

(The alerts field doesn’t exist in the feed when there are no alerts, for anyone down the road…)

I tried both the Initialize Array and the Conditionally Count options and they both work great!

Thanks so much.

1 Like

I’ve found this site to be very helpful in parsing json to give me the proper sytanx: https://jsonpathfinder.com/ just paste in the reply you get from the API and then click around to build the expression

1 Like

Can the map function be nested? Or maybe there is a different way to think about this?

I’m trying to cycle through two nested arrays and I can only grab the first value of the second array. On the below json, I’m trying to cycle through both the ‘games’ array and the ‘promotions’ array. The code that I have is below (which somewhat works as it will grab the 1st promotion name, but won’t grab the second. The code is the same basically as the above, but the difference is that the values in the promotion can be an array.

Thoughts on how to handle?

What I’m looking to return is:
2023 Magnetic Schedule Gate Giveaway
2023 Magnetic Schedule Gate Giveaway
Friday Night Fireworks (this is what isn’t returned in the below code)

schedule = $context.response.data.dates
myString = map(schedule, x.games[0].promotions[0].name)              
join(myString, "\r\n")

The json that the above code is referencing is (coming from here):

{
    "dates": [
        {
            "date": "2023-04-06",
            "games": [
                {
                    "gamePk": 718681,
                    "gameDate": "2023-04-06T23:20:00Z",
                    "officialDate": "2023-04-06",
                    "promotions": [
                        {
                            "name": "2023 Magnetic Schedule Gate Giveaway",
                            "description": "All in attendance will receive a 2023 magnetic schedule. ",
                            "order": 0,
                            "offerType": "Giveaway"
                        }
                    ],
                    "description": "Braves home opener"
                }
            ]
        },
        {
            "date": "2023-04-07",
            "games": [
                {
                    "gamePk": 718676,
                    "gameDate": "2023-04-07T23:20:00Z",
                    "officialDate": "2023-04-07",
                    "promotions": [
                        {
                            "name": "2023 Magnetic Schedule Gate Giveaway",
                            "description": "All in attendance will receive a 2023 magnetic schedule. ",
                            "order": 0,
                            "offerType": "Giveaway"
                        },
                        {
                            "name": "Friday Night Fireworks",
                            "description": "Following every Friday night game, the sky above Truist Park lights up with the #1 rated fireworks show in the Southeast! Every show is different. See them all!",
                            "order": 1,
                            "offerType": "Day of Game Highlights"
                        }
                    ]
                }
            ]
        }
    ]
}

Yes, you could map within a map for nested arrays. Something like the following:

schedule = $context.response.data.dates
names = flatten(map(schedule, map(x.games, map(y.promotions, z.name))))
join(names, ", ")

The second line is doing all the work. Basically iterating through the dates → games → promotions → name which results in a nested array of just the names and then flattens all that nesting into a single top-level array.

There’s some other neat tricks you can do with nested arrays like concatenating data from the top level map iteration in with the lower-level iteration:

schedule = $context.response.data.dates
names = flatten(map(schedule, map(x.games, map(y.promotions, concat(x.date, "▸", z.name)))))
join(names, "\r\n")

Which would result in:

2023-04-06▸2023 Magnetic Schedule Gate Giveaway
2023-04-07▸2023 Magnetic Schedule Gate Giveaway
2023-04-07▸Friday Night Fireworks

(PS. I had to push an update to support this as expressions were previously using matrix iteration which didn’t work with mismatched nested array sizes)

This is great!!!

Can I also grab data not in the top level but at the previous level?

I thought I could do something like this:

schedule = $context.response.data.dates
names = flatten(map(schedule, map(x.games,isEmpty(y.promotions) ? "-" : #// not all games have promotions
 map(y.promotions, concat(z.name, " ( ",formatDate(x.games.gameDate, "ddd"), " | ", formatDate(x.games.gameDate, "h:mm a"),")")))))
join(names, ", ")

I was expecting the output to look like:


Magnetic Schedule Gate Giveaway (Thur | 7:05 pm) #// or whatever the time is
Magnetic Schedule Gate Giveaway (Fri | 7:05 pm) #// or whatever the time is
Friday Night Fireworks (Fri | 7:05 pm) #// or whatever the time is

You can, but you have to reference the variables and properties with the appropriate context.

In your example, y is equivalent to a ‘game’ object (eg. one of the entries in the x.games array), so you would use y.gameDate as the reference.

I just used x, y, z as arbitrary names as most of my map() examples just are working on a single item and x is a generic name. You could use a different variable name if you prefer.

schedule = $context.response.data.dates
names = flatten(map(schedule, map(scheduleEntry.games, isEmpty(game.promotions) ? "-" : #// not all games have promotions
 map(game.promotions, concat(promo.name, " (",formatDate(game.gameDate, "ddd"), " | ", formatDate(game.gameDate, "h:mm a"),")")))))
join(names, "\r\n")
1 Like