Maker.io main logo

IoT Studio Weather Clock

2020-03-10 | By TAYLOR

License: Attribution mikroBUS Click

Introduction

The AVR IoT Home Automation kit is a set of products to quick start your IoT motor applications.  The kit contains an AVR-IoT development board, a Stepper 2 Click, and a stepper motor.  This guide presents a method for creating a weather clock using the Home Automation Kit, Atmosphere IoT Studio, and Amazon Web Services (AWS).  The device fetches weather data from OpenWeatherMap (OWM) for a given location and displays the temperature and condition on a clock face. 

Final testing of the project was done with the enclosure provided by Microchip shown in the image above.

Hardware Setup

Connections for this circuit are simple.  The Stepper 2 Click plugs into the AVR-IoT mikroBus socket and uses the signals shown below. 

Circuit Overview

 

The demo unit from Microchip has an RGB LED strip inside which is directly soldered to the AVR’s 5V power rail and TX pin on the bottom side.  Use of the strip is entirely optional but adds visual flair.  The strip wasn’t available during the development of this tutorial, so it is not covered here. 

 

AWS Setup

The cloud-side code is implemented with two AWS services.  Lambda is used to run a Python function when an event is received from IoT Studio.  S3 is used to store a cache of the Lambda’s results.  This guide assumes you already have your own AWS account.  If you don’t have one, you can sign up to use the Free Tier here

Execution Role

Before creating the Lambda function, an execution role and policy must be created to grant it permission to read and write in the AWS S3 service.  Navigate to the IAM (Identity and Access Management) service.  Select Roles from the Access Management pane on the left.  Press the Create role button. 

AWS Roles

On the following screen, select Lambda under Common use cases. 

Create role

Then press Next button three times to skip adding permissions for now and get to the following screen finish creating the role.

Finish role

 

Permission Policy

Now the role needs permissions to grant it read and write access to S3.  Select the new role in the IAM console and select Add inline policy.

Add policy

Select S3 as the service and make the selections shown below.  This Lambda function will only make use of the Get and Put Object actions for caching. 

Policy

Select Review policy to continue.  On the next screen, give the policy a befitting name and description and select Create policy to finalize it. 

Finish policy

Creating an S3 Bucket

Before writing any code, a space needs to be created to store weather data between OWM API calls.  Navigate to S3 in the AWS console.  Select the Create bucket button from the screen shown below. 

Create bucket

Give it a suitable name and press Next to accept all the default settings until the bucket is created.

Name bucket

Make a note of the new bucket’s name to be used in the next step.

Bucket list

 

Creating the Lambda

Next, create a new Lambda function.  Navigate to the Lambda service within AWS and click the button to create a new function.  Select Author from scratch, give the function a name, and select the Python 3.7 runtime.  In the permissions section, choose the role created earlier to allow the function to access S3. 

New Lambda

Install Dependencies

The update weather function uses the Python Requests library to interact with the OpenWeatherMap API.  Requests isn’t included by default, so it needs to be added to an AWS deployment package. 

Create a directory on your computer and use pip to install the required Python packages there.

 

Copy Code
pip install - -target <path to directory> requests

 

Create a new Python file in the target directory to hold the Lambda code. Make sure to replace the global variable CLOUD_BUCKET with the name of the bucket you created in the previous section.

 

Copy Code
import json
import urllib.parse
import boto3

from datetime import datetime
import os
import requests
import string

# Configs
CLOUD_BUCKET = "your-bucket"
MOTOR_STEP_ANGLE = 1.8
MAX_TEMP = 15
MIN_TEMP = -5

# Setup an instance of AWS S3
s3 = boto3.client('s3')

def sanitize_location(location):
# Replace commas in the zip code locations with underscores
return location.replace(',', '_')


def get_weather_data(location):
url = "http://api.openweathermap.org/data/2.5/weather"
payload = {
#"zip": location,
"id": location,
"units": "metric",
"appid": os.environ['OWM_KEY']
}

r = requests.get(url, params=payload)

if r.status_code != 200:
raise requests.exceptions.HTTPError('Undesired status code returned: {}'.format(r.status_code))

return r.json()


def get_cached_data(location):
filename = sanitize_location(location) + "_cache.json"
resp = s3.get_object(Bucket=CLOUD_BUCKET, Key=filename)
jdata = json.loads(resp['Body'].read())

return jdata


def upload_to_cache(location, data):
json_string = json.dumps(data)
json_string.encode('utf-8')

# Upload and overwrite the current cache on S3
filename = sanitize_location(location) + "_cache.json"
s3.put_object(Bucket=CLOUD_BUCKET, Key=filename, Body=json_string)


def weather2motorposition(weather_data):
# Convert weather temperature and condition to a position on the clock
temperature = weather_data["main"]["temp"]
condition = weather_data["weather"][0]["main"]
code = weather_data["weather"][0]["id"]

# Add in an extra category to match MCHP's demo more closely
if code in (611, 612, 613):
condition = "Sleet"

if temperature > MAX_TEMP:
temperature = MAX_TEMP
elif temperature < MIN_TEMP:
temperature = MIN_TEMP

print("Temp: {}".format(temperature))
print("Condition: {}".format(condition))

# Map condition to starting point on clock face
condition_map = {
"Clear": 0,
"Clouds": 45,
"Drizzle": 90,
"Rain": 135,
"Sleet": 180,
"Snow": 225,
"Thunderstorm": 270,
"Tornado": 315
}

# Convert angle to motor step position
try:
base_position = condition_map[condition] / MOTOR_STEP_ANGLE
except KeyError:
# There are no options for fog, mist, smoke, etc. so just use clouds
base_position = 45
temp_offset = (temperature - MIN_TEMP) / (MAX_TEMP - MIN_TEMP) * (45 / MOTOR_STEP_ANGLE)

return int(base_position + temp_offset)


def format_weather(weather_data):
# Returns the string to be displayed in the app view
temp = str(weather_data["main"]["temp"]) + "C"
desc = string.capwords(weather_data["weather"][0]["description"])

return "Current Weather: {}, {}".format(temp, desc)


def lambda_handler(event, context):
print("Received event: " + json.dumps(event, indent=2))
out_type = event["type"]

try:
location = event["zip"]
except KeyError:
print("Location not included in request.")
print("Using default location of Thief River Falls.")
location = "5049970"

if isinstance(location, int):
# IoT Studio didn't properly convert the payload to a string
location = str(location)

# Get timestamp
now = datetime.now()
timestr = now.strftime("%m/%d/%Y, %H:%M:%S")
print("Current time: {}".format(timestr))

# Load data from the cache
try:
cached_data = get_cached_data(location)
except Exception as e:
print(e)
print("Failed to retrieve cached weather data from S3.")
print("Update will be forced.")

# Check the timestamp on the cached data
try:
time_delta = now.timestamp() - cached_data["timestamp"]
# Convert to minutes
time_delta /= 60
except (KeyError, NameError):
# Cached data isn't available or not timestamped for whatever reason
# Force an update
time_delta = 100

# Only update cache after 20 or more minutes
if time_delta < 20:
print("Returning cached data.")

if out_type == "position":
motor_pos = weather2motorposition(cached_data)
print("New motor position: {}".format(motor_pos))
return motor_pos
elif out_type == "weather":
return format_weather(cached_data)
else:
return ''

# Attempt to get weather data from OWM
print("Updating weather from API...")
try:
weather_data = get_weather_data(location)
except Exception as e:
print(e)
print("Failed to get weather data from OWM.")
raise e

# Add in current timestamp to OWM data
weather_data["timestamp"] = now.timestamp()

# Add in motor position to the data
motor_pos = weather2motorposition(weather_data)
weather_data["motor_position"] = motor_pos
print("New motor position: {}".format(motor_pos))

# Write new weather data to the S3 bucket
upload_to_cache(location, weather_data)

if out_type == "position":
return motor_pos
elif out_type == "weather":
return format_weather(weather_data)
else:
return ''

Zip up the directory using a tool of your choice. 

Download the AWS CLI tools for your system using the instructions found here

Next the CLI tools need credentials to access AWS services.  In the IAM console, select Users, and Add user.  Give the user a fitting name and select the Programmatic access box.

New user

Next on the Permissions screen, choose Attach existing policies directly and select the AdministratorAccess policy.  

User permissions

Click Next on the remaining steps until the user is created.  Two keys will be provided that are needed to authorize the CLI.

Open a command prompt and type

Copy Code
aws configure

Fill out the prompts with your keys and information. 

Finally, use the following command to upload the deployment package to Lambda.  The  - - dry-run flag may be used to test the upload without actually changing the Lambda. 

Copy Code
aws lambda update-function-code - - function-name <your function name> - -zip-file fileb://<your zipped folder>

The results should like something like this:

Results

Open the function in the Lambda console and the dependencies will be visible in the code editor along with the application code. 

Updated Lambda

The final step is to obtain a key from OpenWeatherMap that will allow the Lambda to use their API.  Simply register for a free account on their website to obtain a key.  The key can be added as an environment variable in the console as shown below. 

OWM Key

 

The Lambda function is now ready and can be verified with test events through the AWS console.  Events are expected to be JSON in the form:

Copy Code
{
  "location": " 5049970",
  "type": "position"
}

Where location is a code from OpenWeatherMap.  Zip or postal codes may be used with a slight change to the format of the API request.  Type is either “position” or “weather” and determines whether the response of the Lambda is an integer motor position or the JSON weather data from OpenWeatherMap.  Events from the AVR-IoT always use the “position” type. 

The last piece of setup required for AWS to allow IoT Studio to send events to the Lambda.  Atmosphere already has a document outlining those steps here.  Create the IAM role as described and attach the Lambda policy to the function created above.    

 

IoT Studio Setup

The following section summarizes the functionality of each aspect of the IoT Studio project.  Download the project file at the end of the section for the additional details and settings for each element. 

Embedded

The embedded portion of the project is minimal.  The AVR receives a Cloud Command containing the position it needs to go to which is passed into the Stepper 2 element.  The Stepper 2 element was created for this project and the source files are downloadable at the end of the section.   

A secondary path creates an event that updates the clock on an interval of 20 minutes using the last provided location. 

IoT Studio Embedded

Application

The mobile application portion of the design handles inputs from the user to select a location.  IoT Studio currently has no method for arbitrary user input (i.e. text box) so a selection of buttons is used for location selection.  An initial value may also be assigned in the Variable element in the embedded section above, but it will not be used until after the first interval timeout of 20 minutes. 

IoT Studio App

Each of the input buttons has an associated OWM ID that is sent to the Lambda.  These ID codes are part of the URL in OWM for a specific location.  For Thief River Falls for example, the code is 5049970.

OWM Code

Cloud

The IoT Studio cloud portion of the design acts as a relay and logging layer for the design.  It accepts events from the AVR or the app and relays them to the Lambda function.  Results are logged and sent back to the hardware.  Verify that the details of the Lambda function created above are present in the correct fields of the event and Lambda elements. 

IoT Studio Cloud

Compile the project and download the hex file for the AVR-IoT. 

IoT Studio Project Download:  Weather Clock Project.atmo

AVR-IoT Stepper 2 EEL Source:  stepper2_click_EEL.zip

(Note that this EEL is only for the AVR-IoT board.  It is not compatible with all IoT Studio boards without modification.) 

 

Conclusion

This guide demonstrated how to build a weather clock using the IoT Home Automation kit with IoT Studio and AWS.  Microchip has their own version of this project using MQTT and Google Cloud.  This project is intended to show an alternative approach.  More about the Microchip version of the project can be found in this user guide.

制造商零件编号 IOT-HOME-KIT
IOT HOME AUTOMATION EVAL KIT
DigiKey Kit (VA)
¥657.23
Details
制造商零件编号 324
STEPPER MOTOR HYBRID BIPOLAR 12V
Adafruit Industries LLC
¥125.96
Details
制造商零件编号 MIKROE-1926
STEPPER 2 CLICK
MikroElektronika
¥187.22
Details
制造商零件编号 AC164160
AVR-IOT WG GOOGLE CLOUD EVAL BRD
Microchip Technology
¥328.70
Details
制造商零件编号 PSAC12R-120
AC/DC WALL MOUNT ADAPTER 12V 12W
Phihong USA
¥88.16
Details
Add all DigiKey Parts to Cart
TechForum

Have questions or comments? Continue the conversation on TechForum, DigiKey's online community and technical resource.

Visit TechForum