I have been a user of Home Assistant since version 0.54. Back then we had Sensors for USPS, UPS, and FedEx shipping that used the personal delivery tracking solutions provided by theses services (USPS’ Informed Delivery, UPS’ My Choice, etc). While these sensors worked well enough and they did the job most of the time they were based on Phantom.JS or the chromium driver scraping the data directly from the website. This solution slowly started breaking first with FedEx, the USPS, and finally UPS. FedEx and UPS seem unwilling to provide an API for us to program against instead so the community set into motion trying to find a solution.

A common solution used is this script which has seen many a modification by the community and has no centralized location to track it’s functional changes (read: the original creater released it out into the wild but never put it in GitHub and encouraged PRs). This solution relies on MQTT messages to update an MQTT sensor. While it works, it doesn’t survive a restart of home assistant and requires a script to be always running on your machine. The inherent problem this will face is that the HTML markup of these emails could change any day which would break every implementation of this solution overnight. While this was an elegant first solution I would like, should the community embrace it, to propose a more eligant solution.

Package.py

Introduction

Package.py, so called because of the custom companant’s domain (Package), is a set of AWS services (Lambda, API Gateway, and DynamoDB), a USB Broadband Modem, a script that processes SMS messages, and a custom componant for Home Assistant.

This solution mostly lives in AWS but could, with modification, be done completely locally using whatever backing database you wanted. We start with a “USB Broadband Modem”, the one I used is here. Currently the script to get the data off this modem is… pretty much tied to this device, but with modification you can make it compatible with other devices. See this wonderful blog post that originally made me aware of this solution: http://www.mattiasnorell.com/receive-sms-on-a-raspberry-pi/.

The SMS messages are sent to an AWS Lambda function, by way of an API Gateway, which parses the SMS and stores the data in DynamoDB. On the Home Assistant side, the package componant polls a seperate Lambda via a different API Gateway endpoint. Since the data is persisted in DynamoDB, restarting Home Assistant just re-polls for the data and the state is maintained.

AWS was chosen in part because of the free tier, and in part because I am an Engineer for an Amazon subsidiary that falls under AWS.

  • Lambda always gives you 1 million requests for free a month and up to 3.2 million seconds free per month. These are for life. If you go over these limits with this solution… You should probably order less packages.
  • DynamoDB always gives you 25GB of storage and 25 provisioned read and write capacity units per month. This can handle up to 200 million requests per month. Again, going over this is going to be rather hard unless you are some kind of mailroom.
  • API Gateway gives you 1 Million API Calls Received per month for 12 months. After the first 12 months, this will cost you $3.50 for the first 333 million requests. This will cost you pennies at most.
  • CloudWatch Logs is also used by Lambda to log results of requests. You always get 5GB of Log Data Ingestion and 1 million API requests… It’s text, 5GB is HUGE for text.

As for the cellular plan. I live in the US and have T-Mobile. I gave them a ring and told them I wanted to add a line to my postpaid plan. When I explained that I wanted the line to have no data, no minutes, and unlimited texting it sort of broke the Customer Support person. I explained the gist of what I wanted to do with it (this solution) and she broke even further and connected me to technical support. Technical Support also broke but got the general gist and set me up with a $10 a month line. The modem I have is GSM based and works on T-Mobile via 2G service so I’m absolutely thrilled with the solution.

Installation

The source code is available here: https://github.com/aetaric/package.py

Home Assistant Configuration

This is by far the easiest part of the setup process. Copy the custom_componants folder into the root of your Home Assistant install. In your configuration.yaml file add the following.

1
package:

You can now add the sensors for each carrier you want to track packages for.

1
2
3
4
5
6
7
8
9
10
11
12
13
sensor:
- platform: package
name: USPS Packages
carrier: USPS
- platform: package
name: UPS Packages
carrier: UPS
- platform: package
name: FedEx Packages
carrier: FedEx
- platform: package
name: Amazon Packages
carrier: Amazon

Hold off on restarting Home Assistant until after we have completed the AWS Configuration steps.

AWS Configuration

On the AWS side, we start by creating a DynamoDB table. Pick the region closest to you and spin up a new table. Taking the defaults here will provision a table with 5 read and write capacity units. You can go upto 25 for free, so provision these to the same number of sensors you plan to track in Home Assistant. Your Primary Key here should be tracking_number. DynamoDB is a NoSQL DB so you don’t have to define a schema and the application will just insert the data we want as long as the primary key’s name matches. Once the table is Active, click the radial button next to it and open the Indexes tab. Create a new index with a partition key of carrier so we can perform a lookup on all non-delivered packages by carrier.

Now that we have a DynamoDB table, We need to create two Lambda functions. We’ll start with get_packages_by_carrier. Create a new function, “Author from scratch”, set the runtime to Python 3.7, give it a name and click create function. Now toss the code from packages_by_carrier/lambda_funstion.py into the Function code editor window. Take note of the Execution role as we will need to modify this in Identity and Access Management later to grant access to our DynamoDB table. Now follow these same steps to create a function for package_updates_from_sms and note it’s execution role.

Over to IAM now to grant our execution roles access as we said we would. Find one of the roles and click on it’s name to bring up the role summary. You can either modify the AWSLambdaBasicExecutionRole policy or attach a new one I modified mine, but if you choose to attach one it’s the same basic steps. Edit the policy and add additional permissions choose a service and pick DynamoDB. Give it the following permissions:

  • List
    • ListTables
  • Read
    • ListStreams
    • BatchGetItem
    • ConditionCheckItem
    • GetItem
    • GetRecords
    • GetShardIterator
    • Query
    • Scan
  • Write
    • BatchWriteItem
    • DeleteItem
    • PutItem
    • UpdateItem
    • UpdateTable
    • UpdateTimeToLive

Resolve the resource ARN warnings. You can specify the “any” region and then provide the table name. Review this policy, save it, and repeat the process for the other Role.

Now we need to modify the SMS Gateway-swagger-apigateway.json file and specify the ARNs of the two Lambda functions so that our API will work. Look for the uri options (the one under POST is our package_updates_from_sms function) and insert the ARNs in the right location.

In API Gateway, create a new REST API and “Import from Swagger or Open API 3”. Paste in our modified SMS Gateway-swagger-apigateway.json file and hit Import. Be sure your Endpoint Type is regional to avoid the charges for “edge optimized” APIs.

With this, we are done setting up the AWS services! Your package sensors should now be able to update without issue! If you have issues feel free to drop me a message on Discord and I’ll see if I can help out.