Sending Weather Alerts to Slack

Last week I published an article called Sending Weather Alerts to Netcool. One of the comments underneath my LinkedIn post suggested that it may have been better to send the alerts to Slack and so this week I thought I would create a second version of the blog to do just that. Sorry for some of the repetition from the last blog but I assumed that many people will come straight here (via google) and therefore I didn’t want to miss pertinent information I published last time.

If you are serious about monitoring your business services it is not enough to just look at the performance of your traditional IT hardware and software services. You also need to look at external forces that may affect your business from weather to social media to news sentiment. I’ve recently been looking at all of these subjects but this blog will concentrate on a simple method for monitoring the future weather at your business locations. For some businesses this is more important than others; if you have remote sites (such as ATMs, cell sites, factories, data centres, distribution centres, etc) that could cause loss of service or supply chain issues if they are out of service then you should be monitoring the weather in those locations so that you can mitigate these issues. As climate change becomes more extreme these issues will only become more prevalent with larger and more frequent storms, more flooding and an increased likelihood of Forest Fires.

Even in the UK, a country not noted for weather extremes, we are starting to see these issues become more frequent. I worked at a company that had positioned its main data centre in Tewkesbury which was fine until in 2007 there were major floods which were repeated in 2012 and 2019.

In 2016 the Vodafone UK data centre was flooded over Christmas which lead to a disruption to their services.

With this in mind, this blog shows a simple way of using a python script to monitor the forecasted weather in your locations. The script then sends the alerts to a Slack channel as messages.

Getting the Data

There are a few APIs that offer weather information, but I chose OpenWeatherMap.org as they offered a free starting tier that allows for development with up to 60 calls/minute and 1,000,000 calls/month. This may not be enough for your requirements, but you can see the pricing here if you want to make more calls or get more detailed data. The other advantage to openweathermap.org is that they offer what they call “one callAPI that provides the following weather data for any geographical coordinates:

  1. Current weather
  2. Minute forecast for 1 hour
  3. Hourly forecast for 48 hours
  4. Daily forecast for 7 days
  5. National weather alerts
  6. Historical weather data for the previous 5 days

The 2 items that were important for this purpose were the daily forecast and National weather alerts that are sent from major national weather warning systems. These latter alerts are generally provided in English but as they are local warnings they could be in the local language of the provider.  Items 1-3 and 6 are not useful for alerting and can be excluded from the API call which is made as follows:

https://api.openweathermap.org/data/2.5/onecall?lat={lat}&lon={lon}&exclude={part}&appid={API_key}

Note:   You will need to register to get the API key before you can use it.

Therefore, we want 2 types of data:

Daily

The daily data is provided as follows (the data has been cut down for simplicity):

'daily': [
{'dt': 1634616000, 'sunrise': 1634597595, 'sunset': 1634639375, 'moonrise': 1634637300, 'moonset': 1634592720, 'moon_phase': 0.45, 'temp': {'day': 296.18, 'min': 292.32, 'max': 297.54, 'night': 295.74, 'eve': 296.97, 'morn': 292.44}, 'feels_like': {'day': 296.71, 'night': 296.43, 'eve': 297.57, 'morn': 292.75}, 'pressure': 1017, 'humidity': 83, 'dew_point': 293.14, 'wind_speed': 2.93, 'wind_deg': 319, 'wind_gust': 5.79, 'weather': [{'id': 804, 'main': 'Clouds', 'description': 'overcast clouds', 'icon': '04d'}], 'clouds': 100, 'pop': 0.24, 'uvi': 0},
{'dt': 1634702400, 'sunrise': 1634684019, 'sunset': 1634725730, 'moonrise': 1634725680, 'moonset': 1634682180, 'moon_phase': 0.5, 'temp': {'day': 302.93, 'min': 295.13, 'max': 305.06, 'night': 298.19, 'eve': 301.62, 'morn': 295.42}, 'feels_like': {'day': 306.56, 'night': 299.18, 'eve': 305.07, 'morn': 296.18}, 'pressure': 1013, 'humidity': 65, 'dew_point': 295.65, 'wind_speed': 5.84, 'wind_deg': 143, 'wind_gust': 9.7, 'weather': [{'id': 804, 'main': 'Clouds', 'description': 'overcast clouds', 'icon': '04d'}], 'clouds': 100, 'pop': 0.21, 'uvi': 0},
{'dt': 1634788800, 'sunrise': 1634770443, 'sunset': 1634812086, 'moonrise': 1634814120, 'moonset': 1634771640, 'moon_phase': 0.52, 'temp': {'day': 291.55, 'min': 291.55, 'max': 298.05, 'night': 291.98, 'eve': 292.32, 'morn': 297.58}, 'feels_like': {'day': 291.72, 'night': 291.82, 'eve': 292.22, 'morn': 298.59}, 'pressure': 1017, 'humidity': 87, 'dew_point': 289.42, 'wind_speed': 8.77, 'wind_deg': 36, 'wind_gust': 15.51, 'weather': [{'id': 502, 'main': 'Rain', 'description': 'heavy intensity rain', 'icon': '10d'}], 'clouds': 100, 'pop': 1, 'rain': 37.5, 'uvi': 0},
…
{'dt': 1635220800, 'sunrise': 1635202572, 'sunset': 1635243881, 'moonrise': 1635258660, 'moonset': 1635219300, 'moon_phase': 0.67, 'temp': {'day': 295.95, 'min': 293.24, 'max': 296.42, 'night': 294.24, 'eve': 296.17, 'morn': 293.33}, 'feels_like': {'day': 296.06, 'night': 294.13, 'eve': 296.36, 'morn': 293.47}, 'pressure': 1019, 'humidity': 68, 'dew_point': 289.84, 'wind_speed': 4.77, 'wind_deg': 17, 'wind_gust': 10.15, 'weather': [{'id': 804, 'main': 'Clouds', 'description': 'overcast clouds', 'icon': '04d'}], 'clouds': 90, 'pop': 0, 'uvi': 0}]}

Alerts

And the alerts data is provided as follows:

    "alerts": [
    {
      "sender_name": "NWS Tulsa",
      "event": "Heat Advisory",
      "start": 1597341600,
      "end": 1597366800,
      "description": "...HEAT ADVISORY REMAINS IN EFFECT FROM 1 PM THIS AFTERNOON TO\n8 PM CDT THIS EVENING...\n* WHAT...Heat index values of 105 to 109 degrees expected.\n* WHERE...Creek, Okfuskee, Okmulgee, McIntosh, Pittsburg,\nLatimer, Pushmataha, and Choctaw Counties.\n* WHEN...From 1 PM to 8 PM CDT Thursday.\n* IMPACTS...The combination of hot temperatures and high\nhumidity will combine to create a dangerous situation in which\nheat illnesses are possible.",

      "tags": [
        "Extreme temperature value"
        ]
    },
    ...
  ]

Data Analysis and Conversion

There are a few things to note about this data.

  1. Firstly, all dates are provided as Unix time (UTC zone) which need to be converted.

e.g.

timestamp = datetime.datetime.fromtimestamp(time)
curr_time = timestamp.strftime('%d/%m/%Y %H:%M:%S')

*where time is the Unix time.

  1. Secondly, if the forecast does not include rain or snow, they are simply not returned at all so you need to test for their existence before getting the value.
  2. All data is returned as standard metric measurements by default. This can be changed to imperial measurements, but this is important as the default temperature is Kelvin (which I convert to Celsius) and wind speed is metre/sec (which I leave) rather than miles per hour.
  3. As you may have noticed the OpenWeatherMap one call API call needs to be made using longitude and latitude instead of a location name. This has its advantages as it gives some accuracy to the location but is a pain for my script as I wanted to create the config file using place names for simplicity. Therefore to do this I used the Nominatim API. Nominatim is a geocoder that can identify geometries in OSM data corresponding to a given string. In this example Python code I supply a city name and get 2 variables back: location.latitude and location.longitude. Occasionally this API times out and so you would need to allow for that in your script.

e.g.

import geopy
from geopy import Nominatim
city_name = “Bangkok”
locator = Nominatim(user_agent="myGeocoder")
location = locator.geocode(city_name)
logging.info("Latitude: {}, Longitude: {}".format(location.latitude, location.longitude))

Slack Integration

To complete this integration you will need to be the admin in your Slack workspace. In my case, I had an Orb Data Workspace and I created a new channel called weather-alerts that I wanted to send my weather warnings.

In your workspace choose Settings & administration->Manage apps and in the resulting screen choose Build at the top. This will give you a screen like this.

Click Create New App (or Create an App if this is the first app).  In the resultant window choose From Scratch.


And then choose the name for the app and the workspace you want to develop in before clicking the Create App button.In the Add features and functionality section on the next screen select Incoming WebHooks.

This will open a  screen that looks like this.

Ensure that the Activate Incoming Webhooks switch is set to on and then at the bottom make a note of the URL as we will need this for our config section.


Lastly, you will need to add this app to a Workspace (to send the messages to) and choose a channel to which they will appear. To do this click the Add New WebHook to Workspace button, choose the channel from the drop-down list and click Allow. This will complete the Slack part.

Back in Python, you need to add a section in your script to send the message/alert.  The code is relatively simple and relies on the requests library being imported. You also need to ensure that you encode the data you send as a UTF-8 string as the default encoding for HTTP POST is ISO-8859-1 aka Latin-1.

import requests
#########################
# slack alert
#########################
def slack_alert(alert_string):
    logging.info("Sending Slack Alert from " + city_name)
    payload = '{"text":"%s"}' % alert_string
    response = requests.post(slack_url,)
    logging.info("Slack Response Code: " + response.text)

Configuration

I wanted to make the configuration file (in my case config.ini) as simple as possible and so it has only 5 sections. I may add more based on customer requirements later but for now this is enough. These are the sections:

GENERAL

This section has a polling interval, a customer name to be used if you have multiple customers (as we do) and an alerts parameter to allow for the Critical National Weather alerts to be turned off. The polling interval in my example is 15 minutes (900 seconds) but this could be much larger as the forecast does not change too often. I have opted to allow for slack alerts to be turned on or off as well as socket alerts which in the last blog were hard coded to always be on.

e.g.

[GENERAL]
poll=900
customer=ORB
alerts=TRUE
slack=TRUE
socket=TRUE

SLACK

The slack section contains the URL we previously created when we configured Slack itself. I’ve changed the values as I don’t want random messages on our channel.

[SLACK]
url = "https://hooks.slack.com/services/TNxxxxxxx/yyyyyyyyyyyyyyyy/zzzzzzzzxxxxxxxxxxxx"

GEO

The GEO section is a comma-separated list of placenames. You can put in as many as you want but remember the limitation on calls per day. e.g.

[GEO]
locations=Tokyo,Kamchatka Krai,Bronx

LOGFILE

The log parameter contains the location of the log.

e.g.

[LOGFILE]
log=weather.log

SERVER

This section is the location of the server containing a socket probe to receive any alerts. This is an IP/Hostname and a port.

e.g.

[SERVER]
host = 191.1.1.1
port = 9595

DAILY_THRESHOLDS

As I said earlier the script checks both the daily forecasts and national alerts. The alerts do not need any configuration but to create alerts from the forecasts you need to add thresholds for what you consider unacceptable conditions. Most of the parameters are greater than (gt) but I also included one less than (lt) for temperature as a sudden freeze could also include issues for remote sites both for access and potential damage to IT equipment.  You can add your thresholds as you see fit. Remember when setting these values that they need to match the units you are retrieving the data (e.g. metres per second for wind speed).

e.g.

[DAILY_THRESHOLDS]
rain_gt = 10
snow_gt = 10
wind_gt = 12
temp_gt = 40
temp_lt = -5
humidity_gt = 90
pressure_gt = 1000

Execution and Data Flow

The simplest way to describe how the script works is to look at the flowing whiteboard diagram.

Let’s start with the execution of the script.

  1. The script can be executed anywhere as either a long-running daemon type process or scheduled intermittently as a one-off. I chose the former as was easier to allow for the polling interval to be added to the config file (which I called config.ini). Once it is run it looks for the config file and gets the GEO locations to report on, the thresholds to alert upon and the location to send alerts to. In my example, this script can be located anywhere, even on a laptop with internet access.
  2. The locations are retrieved and converted into longitude and latitude from the Nominatim API.
  3. For each named location (now longitude and latitude) the One Call API is run against OpenWeatherMap.org and the data is retrieved.
  4. The script will then cycle through this data and based on thresholds retrieved from the config file send Warning alerts via a socket connection to the Socket Probe. I have used this as an example, but the alerts could be sent using any other mechanism such as email or via another monitoring products API. It will also check if there are any National Weather alerts and send those too.
  5. The alerts are then forwarded as messages via a WebHook to a Slack Channel…
  6. The Slack channel should now show these messages.

In this example, at the bottom, you can see both the threshold driven alerts and the National Weather alerts. For the Bronx in New York, we can see that these alerts correlate in that we have a Severe Weather Advisory for potential flooding (Flood Advisory) and a threshold alert for heavy rain of 95.32mm in a day on the 26th of October. Let’s hope the forecast is wrong.

It’s also possible to extend the Slack app to add more details. In the example below, I have tidied up the output, added an icon based on the forecast, created a link for more information based on the longitude and latitude plus added some buttons to allow for acknowledgement or even getting more details.

I haven’t provided the script in this blog as this work was created for a customer however if you would like something similar, we can modify the work to meet your requirements and provide a completed solution. Alternatively, feel free to create your solution based on the ideas I have shown here.

If you have questions, please don’t hesitate to contact me.

Hits: 41