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.
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:
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')
.goto('https://parkmobile.io/login')
.cookies()
await chromeless
.goto('https://dlweb.parkmobile.us/Phonixx/')
.setCookies(cookies)
.type(process.env.LOGIN, INPUT.LOGIN)
.type(process.env.PASSWORD, INPUT.PASSWORD)
.wait(500)
.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:
functions:
handler:
events:
- schedule: rate(5 minutes)
For the curious, here is the github repository for the project.