• 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 https://dlweb.parkmobile.us/Phonixx/. However, navigating to this page directly does not work. Instead, you need to navigate to https://parkmobile.io/login and click "Zone Parking". That's because the parkmobile.io/login 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')
        .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.