Sending a Weather Forecast with an AWS Lambda and Twilio

Recently I needed to get a daily weather forecast, but wanted to get it over text message rather than an app or email.

After looking at a few technologies, I realized I could build my own daily alert with a little JavaScript.

This post is going to cover how I setup a daily weather forecast that was delivered via AWS Lambda and Twilio. My project was written in JavaScript and uses the AWS node SDK.

An open source version of my project is available on GitHub here.

The Overall Flow

The project that I built has the following flow:

  1. Lambda is triggered by a CloudWatch scheduled cron job
  2. Lambda calls the OpenWeatherMap API for current conditions
  3. Lambda calls the NOAA API for forecast data
  4. A message is created with all of the content
  5. A text message is sent via Twilio’s APIs

Creating the AWS Lambda and ClaudiaJS

To create the Lambda that I used, I used the ClaudiaJS CLI. ClaudiaJS makes creating Lambdas super simple because it saves you time and effort from working in the AWS console. The CLI enables you to use prebuilt scripts with parameters to build and deploy your infrastructure. Once you’ve built a Lambda, ClaudiaJS also enables you to update it and test it locally. ClaudiaJS has lots of great tutorials, and I recommend you start with the tutorial here.

I also have written a separate post that goes into more detail about using ClaudiaJS to build a serverless API here.

Following the ClaudiaJS guidelines I used the basic claudia create and claudia update commands. In order to do this yourself, you'll first need to have ClaudiaJS CLI and the AWS CLI setup.

Once you’ve got the initial setup, do a git clone of my project. Please consult my project's README on how to setup the environment variables.

Once you’ve added the environment variables to your terminal’s profile, you can use the project’s npm scripts to do the rest.

In the terminal, navigate to where you cloned the project and run npm run create-lambda.

If you want to do updates to the lambda, you can also use my npm script with npm run update-lambda, and that will apply any code changes directly to your Lambda in AWS.

Once your Lambda is built, you can also test it with ClaudiaJS using npm script in my project. In the terminal just run npm run local-test to test your Lambda locally.

Getting the Weather Forecast

In my Lambda I retrieved the forecast from both the OpenWeatherMap API and NOAA with the following:

const APIKey = process.env.OPEN_WEATHER_MAP_API_KEY;
const latitude = process.env.LATITUDE;
const longitude = process.env.LONGITUDE;
const units = 'imperial';

// OpenWeatherMapAPI
const openWeatherMapAPIURL = 'https://api.openweathermap.org/data/2.5/weather?lat=' + latitude + '&lon='
+ longitude + '&units=' + units + '&appid=' + APIKey;
const currentWeather = await axios.get(openWeatherMapAPIURL)
.catch((error) => {
console.log(error);
return;
});

// NOAA Metadata
const NOAAMetadata = await axios.get('https://api.weather.gov/points/' + latitude + ',' + longitude)
.catch((error) => {
console.log(error);
return;
});

// NOAA Weekly
const NOAAWeeklyForecast = await axios.get(NOAAMetadata.data.properties.forecast)
.catch((error) => {
console.log(error);
return;
});

// NOAA Hourly
const NOAAHourlyForecast = await axios.get(NOAAMetadata.data.properties.forecastHourly)
.catch((error) => {
console.log(error);
return;
});

If you notice, I’m using axios here to do the HTTP calls. This made each call super simple and easy to work with.

I used the Latitude and Longitude coordinates of the location I wanted weather for to make the calls. I first called the OpenWeatherMap API to get current weather conditions. I then called the metadata, weekly forecast, and hourly NOAA endpoints to get the forecast.

Once I successfully retrieved the weather information, it was just a matter of parsing the response as you see here:

const hoursToday = retrieveHours(NOAAHourlyForecast.data.properties.periods);

let highTemp = 0;
hoursToday.forEach((period) => {
if(parseInt(period.temperature) > highTemp) {
highTemp = period.temperature;
}
});

let lowTemp = highTemp;
hoursToday.forEach((period) => {
if(parseInt(period.temperature) < lowTemp) {
lowTemp = period.temperature;
}
});

const sunrise = formatTime(currentWeather.data.sys.sunrise);
const sunset = formatTime(currentWeather.data.sys.sunset);
const message =
'WEATHER TEXT:\n'
+ '\n'
+ 'Good Morning! ☀️ 💦 🌤 ⛈ \n'
+ 'Here\'s the lowdown for today...\n'
+ '\n'
// to show degree symbol on OSX hold shift + option + 8
+ 'temp: ' + currentWeather.data.main.temp.toFixed(0) + '°\n'
+ 'high: ' + highTemp.toString() + '°\n'
+ 'low: ' + lowTemp.toString() + '°\n'
+ 'wind: ' + currentWeather.data.wind.speed.toFixed(0) + ' MPH\n'
+ 'sunrise: ' + sunrise + ' AM\n'
+ 'sunset: ' + sunset + ' PM\n'
+ '\n'
+ 'forecast: ' + NOAAWeeklyForecast.data.properties.periods[0].detailedForecast + '\n'
+ '\n'
+ 'Have a good day! 🎉🎉 🎉 🎉';

Note I also used a helper function for the sunrise and sunset values. These were just to format the date representation as a number that was returned by the OpenWeatherMap API. I know there are more elegant ways to handle this, but my small functions achieved what I was looking for. Note that the OpenWeatherMap API returns the time in terms of UTC, so you'll need to accommodate that in your message based on your timezone.

I also created a helper function to retrieve the hour periods from the hourly forecast specific for today. I had originally intended to use the OpenWeatherMapAPI for the high and low temp, but found that I had to subscribe to their paid service for that. Unfortunately, the NOAA API for current conditions is also unreliable because some weather stations do not regularly update. So to resolve this I used the (reliable) NOAA hourly forecast endpoint, and just parsed the data to determine the expected high and low of the day.

Sending the actual Message

I should note that I spent a fair amount of time playing with AWS SNS service. My original idea was to leverage an SNS topic that could send a message to a phone number (and email). The issue I found was that AWS has a limit on the number of SNS Topic messages that could be published to phone numbers. This is for security and to avoid people abusing the text message functionality. Since there was a limit, I wanted something that I could have (potentially) infinite abilities to use. So I looked at Twilio.

Twilio provides a great service that enables both voice and text service through their APIs. To get started with them is super easy. You just create a free trial account, and they give you a number to start using. With this number you can send both text and voice messages. Twilio has great documentation and I recommend checking out the site here.

When you first get setup with Twilio, you have a trial account. If you upgrade your account, you can have a “pay as you go” access. You also can reserve the number that you were given in the trial period. Both of these options are relatively inexpensive and easy to setup.

For the purpose of this project, I just needed to use the text message functionality. So I ended up just doing the following:

let response = 'lambda completed with ';

await client.messages
.create({
body: message,
from: process.env.TWILIO_FROM,
to: process.env.TWILIO_TO
})
.then((success) => {
console.log(success.sid);
response = response + 'success';
})
.catch((error) => {
console.log(error);
response = response + ' error';
});

return response;

I included the response object just to be able to capture something to return when I tested my lambda.

Calling the Lambda

Closing Thoughts

Feel free to comment and follow me on Twitter at @AndrewEvans0102!

(cover image source is pixabay)

Originally published at https://dev.to on September 9, 2019.

Husband, Engineer, OSS Contributor, and Manager at CapTech Consulting. Follow me on https://rhythmandbinary.com and https://andrewevans.dev