AWS Compute Blog
Build a serverless Martian weather display with CircuitPython and AWS Lambda
Build a standalone digital weather display of Mars showing the latest images from the Mars Curiosity Rover.
This project uses an Adafruit PyPortal, an open-source IoT touch display. Traditionally, a microcontroller is programmed with firmware compiled using various specific toolchains. Fortunately, the PyPortal is programmed using CircuitPython, a lightweight version of Python that works on embedded hardware. You just copy your code to the PyPortal like you would to a thumb drive and it runs.
I deploy the backend, the part in the cloud that does all the heavy lifting, using the AWS Serverless Application Repository (SAR). The code on the PyPortal makes a REST call to the backend to handle the requests to the NASA Mars Rover Photos API and InSight: Mars Weather Service API. It then converts and resizes the image before returning the information to the PyPortal for display.
Prerequisites
You need the following to complete the project:
- An Adafruit PyPortal.
- A microSD card.
- An AWS account. This project can be completed using the AWS Free Tier.
Deploy the backend application
Using a serverless backend reduces the load on the PyPortal. The PyPortal makes a call to the backend API and receives a small JSON object with the relevant data. This allows you to change to the logic of where and how to get the image and weather data without needing physical access to the device.
The backend API consists of an AWS Lambda function, written in Python, behind an Amazon API Gateway endpoint. When invoked, the FetchMarsData function makes requests to two separate NASA APIs. First it fetches the latest images from the Mars Curiosity Rover, typically from the previous day, and picks one at random. It resizes and converts the image to bitmap format before uploading to Amazon S3 with public read permissions. The PyPortal downloads the image from S3 later.
The function then calls the InSight: Mars Weather Service API. It retrieves the average air temperature, wind speed, pressure, season, solar day (sol), as well as the first and last timestamp of daily sampling. The API returns these values and the S3 image URL as a JSON object.
I use the AWS Serverless Application Model (SAM) to create the backend. While it can be deployed using the AWS SAM CLI, you can also deploy from the AWS Management Console:
- Generate a free NASA API key at api.nasa.gov. This is required to gain access to the NASA data APIs.
- Navigate to the aws-serverless-pyportal-mars-weather-display application in the Serverless Application Repository.
- Choose Deploy.
- On the next page, under Application Settings, enter the parameter, NasaApiKey.
- Once complete, choose View CloudFormation Stack.
- Select the Outputs tab and make a note of the MarsApiUrl. This is required for configuring the PyPortal.
- Navigate to the MarsApiKey URL listed in the Outputs tab.
- Click Show to reveal the API key. Make a note of this. This is required for authenticating requests from the PyPortal to the MarsApiUrl.
PyPortal setup
- Follow these instructions from Adafruit to install the latest version of the CircuitPython bootloader. At the time of writing, the latest version is 5.2.0.
- Follow these instructions to install the latest Adafruit CircuitPython library bundle. I use bundle version 5.x.
- Update the onboard Wi-Fi coprocessor using these instructions.
- Insert the microSD card in the slot located on the back of the device.
- Optionally install the Mu Editor, a multi-platform code editor and serial debugger compatible with Adafruit CircuitPython boards. This can help if you need to troubleshoot issues.
- Optionally if you have a 3D printer at home, you can print a case for your PyPortal. This can protect your project while also being a great way to display it on a desk.
Code PyPortal
As with regular Python, CircuitPython does not need to be compiled to execute. Flashing new firmware on the PyPortal is as simple as copying a Python file and necessary assets over to a mounted volume. The bootloader runs code.py
anytime the device starts or any files are updated.
- Use a USB cable to plug the PyPortal into your computer and wait until a new mounted volume
CIRCUITPY
is available. - Download the project from GitHub. Inside the project, copy the contents of /circuit-python on to the
CIRCUITPY
volume.
- Inside the volume, open and edit the
secrets.py
file. Include your Wi-Fi credentials along with the MarsApiKey and MarsApiUrl API Gateway endpoint, which can be found under Outputs in the AWS CloudFormation stack created by the Serverless Application Repository. - Save the file, and the device restarts. It takes a moment to connect to Wi-Fi and make the first request.
Optionally, if you installed the Mu Editor, you can click on “Serial” to follow along the device log.
Understanding how CircuitPython calls API Gateway
The main CircuitPython file is code.py. At the end of the file, the while loop periodically performs the operations necessary to display the photos from the Curiosity Rover and the InSight Mars lander weather data.
while True:
data = callAPIEndpoint(secrets['mars_api_url'])
downloadImage(data['image_url'])
showDisplay(data['insight'],
displayTime=60*interval_minutes)
First, it calls the API Gateway endpoint using the URL from the secrets.py
file, and passes the returned JSON to helper functions. The callAPIEndpoint(url)
function passes the MarsApiKey in the header and a timeout of 30 seconds to the wifi.get()
method. The timeout is required for integrations with services like Lambda and API Gateway. Remember, the CircuitPython code is running on a microcontroller and sometimes must wait longer when making requests.
def callAPIEndpoint(mars_api_url):
headers = {"x-api-key": secrets['mars_api_key']}
response = wifi.get(mars_api_url, headers=headers, timeout=30)
data = response.json()
print("JSON Response: ", data)
response.close()
return data
The JSON object that is received by the PyPortal is defined in the handler of the Lambda function. In the GitHub project downloaded earlier, see src/app.py.
def lambda_handler(event, context):
url = fetchRoverImage()
imgData = fetchImageData(url)
image_s3_url = resize_image(imgData)
weatherData = getMarsInsightWeather()
return {
"statusCode": 200,
"body": json.dumps({
"image_url": image_s3_url,
"insight": weatherData
})
}
Similar to the CircuitPython code, this uses helper functions to perform all the various operations needed to retrieve and craft the data. At completion, the returned JSON is passed as the response to the PyPortal.
A quick way to add a new property is to edit the Lambda function directly through the AWS Lambda Console. Here, a key “hello” is added with a value “world”:
In the CircuitPython code.py
file, the key is now available in the JSON response from API Gateway. The following prints the key value, which can be seen using the Mu Editor Serial debugger.
data = callAPIEndpoint(secrets['mars_api_url'])
print(data[‘hello’])
The Lambda function is packaged with the AWS Python SDK, boto3, which provides methods for interacting with a variety of AWS services. The Python Requests library is also included to make calls to the NASA APIs. Try exploring how to incorporate other services or APIs into your project. To understand how to modify the visual display on the PyPortal itself, see the displayio guide from Adafruit.
Conclusion
I show how to build a “live” Martian weather display using an Adafruit PyPortal, CircuitPython, and AWS Serverless technologies. Whether this is your first time using hardware or a serverless backend in the AWS Cloud, this project is simplified by the use of CircuitPython and the Serverless Application Model.
I also show how to make a request to API Gateway from the PyPortal. I then craft a response in Lambda for the PyPortal. Since both use variants of the Python programming language, much of the syntax stays the same.
To learn more, explore other devices supported by CircuitPython and the variety of community contributed libraries. Combined with the breadth of AWS services, you can push the boundaries of creativity.