Creating a Bot to Refill Parking Meters Using AWS Lambda

October 7, 2019

I get a shameful number of parking tickets from failing to refill parking meters on time. So one day I decided to make a bot that tops off the parking meter for me. Yes, I'm probably breaking some terms and conditions. But for now that's a risk I'm willing to accept.

So here's the situation. My car is parked on the street in a suburb of Washington DC. I need to refill the parking meter every two hours. These meters limit the parking reservation limit to two hours. So every two hours during business hours (9 AM to 6 PM, Monday to Friday) I need to refill the meter.

ParkMobile is one of a few apps that allow you to pay for parking on a device. But even with a convenience of re-reserving your parking space remotely from your computer of phone, it's still a pain to do every two hours on weekdays.

Here's what the ParkMobile UI looks like for reserving a parking space:

It's not too complicated at first glance.

You just need to set the zone id, select a location, selection a parking duration, select a time block and then click start parking.

Browser Automation

I opted to use chromeless to automate reserving a parking space. Chromeless is a browser automation library with an API akin to nightmare.js that uses Chrome Canary instead of Electron.

My rationale for using Chromeless is that I wanted to run the lot in AWS Lambda and not locally on my machine for obvious reasons.

The hardest part turned out to be logging in to ParkMobile. I encountered the following obstacles automating the login flow:

  • The actual ParkMobile login page (the page where you are prompted to enter your username and password) is However, navigating to this page directly does not work. Instead, you need to navigate to and click "Zone Parking". That's because the link sets a myriad of cookies (e.g., anti-csrf) that are needed.
  • ParkMobile did not like the default user-agent "Chromeless" (for obvious reasons). So I manually set the user-agent to something plausible.

Here's what the code looks like for logging in:

const INPUT = {
  LOGIN: 'input#ctl00_ContentPlaceHolder1_UcUserLoginControl1_userName',
  PASSWORD: 'input#ctl00_ContentPlaceHolder1_UcUserLoginControl1_password'
const login = async (chromeless) => {
  let cookies = await chromeless
    .setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36')
  await chromeless
    .type(process.env.LOGIN, INPUT.LOGIN)
    .type(process.env.PASSWORD, INPUT.PASSWORD)
    .evaluate(() => {
      WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions("ctl00$ContentPlaceHolder1$UcUserLoginControl1$lbLogon", "", true, "", "", false, true))

The code above logins into ParkMobile. Here's what it looks like when run:

I won't bore you with the remaining implementation details. In brief I deployed the project to AWS lambda using serverless. I scheduled it to run every 5 minutes and used redis to keep track of state. I also used Twilio to send myself confirmation text messages every time the space was reserved.

AWS lambda makes cron jobs trivially easy:

      - schedule: rate(5 minutes)

For the curious, here is the github repository for the project.