FordPass

lumenphpawsfordapioauthbrefserverless

FordPass iOS App Preview

This project was born out of frustration with Ford and their less-than-ideal FordPass app. The ability to start my car remotely on a cold Monday morning, before taking off, is one of the primary reasons I decided to go with Ford this time around. Their app is free of charge and provides that basic functionality to the vehicle I wanted.

However, the lack of integration with internal iOS systems like Siri or shortcuts made it so that the only way to access those features was through the app. That meant finding the app, waiting for it to load, and then, finally, holding on to the start button for 3 seconds for the command to be sent to the car. Even with 1GB download speed, the process takes around 30 seconds to over 1 minute!

So there was a problem. I just want to ask Siri to start my car, surely it is not too much to ask for. Then it occurred to me that I could look it up on Github, and indeed, looking up FordPass on Github will yield several results that seem like great fits to solve the problem I was having.

Existing Solutions

Github Search for FordPass

Shoutout to tonesto7/fordpass-scriptable. This repository is great. The only reason I opted to not use this particular code base was due to scriptable dependency and while it wasn't a huge deal it ended up just offering more than what I wanted with the widgets.

Implementation

Knowing it could be done, I went to the drawing board. The simple breakdown would look like this: the app needed to be authenticated with Ford's API using my personal FordPass account and have my car's VIN, this would enable the ability to make calls to Ford's endpoints which would trigger the various commands.

For the tech stack, I chose Lumen. While it is no longer recommended it made initial sense, and gave me the essentials of Laravel in a tiny package (good for serverless deployments). I only needed the router, a controller, and my service classes (which are agnostic to the Framework they would run on ~ kinda).

There were two interfaces I wanted to implement. A command-line interface for local debugging and exposed API endpoints. The API endpoint would be hosted and exposed on AWS as a Lambda Function. Both would use the same basic building blocks of the Service class with slightly different outputs. With it being a serverless function there is no easy way to access the CLI on AWS. Not a huge problem since the code could run on any machine.

Command-Line Interface

The CLI exposed more information. I wanted a simple console that would output the vehicle's data available from Ford. An example output would look like:

$ php artisan vehicle:status
šŸš— VIN: 1FTEX14H0RKA51281
āš™ļø  Ignition status: IGNITION_ON
āœ… Remote Start is on
Remote Start: 0 (running since: 2021-01-01T00:00:00Z)
šŸ”’ Lock: DOORS_LOCKED (2021-01-01T00:00:00Z)
šŸ”” Alarm: ALARM_OFF (2021-01-01T00:00:00Z)
ā±  Odometer: 1000 (2021-01-01T00:00:00Z)
ā›½ļø Fuel: 100% (until empty: 1000)
šŸ“ Location: lat 0 / long 0
šŸŒŽ Map View: https://duckduckgo.com/?q=0,0&ia=web&iaxm=maps
šŸ”‹ Battery Health: āœ… (STATUS_GOOD)
šŸ›¢  Oil Life: 100%
šŸŒ”  Tire Pressure: āœ…
----------------------------------
Last Refresh: 2021-01-01T00:00:00Z
Last Modified: 2021-01-01T00:00:00Z
Server Time: 2021-01-01T00:00:00Z

API

The heart of the application was the API, since most of the interactions required would be triggered by the Siri shortcuts hitting the necessary endpoints. The shortcut was pretty simple, this is what the setup looked like in Shortcuts.

  1. Receive the voice input "Start my car"
  2. Go to the AWS Lambda function endpoint
  3. Parse response looking for "status":200
  4. If not successful provide the necessary feedback.

Siri Shortcuts Preview

So if I wanted to start my car, I could request Siri to "Start my car", it would then hit my endpoint, https://[aws-lambda].amazonaws.com/api/vehicle/{vin}/start, which would trigger the start command for the car:

SendVehicleCommandAction.php

// ...

use App\Services\Ford\Vehicle;

//...

public function __invoke(string $vin, string $command, Vehicle $service)
{
    if (in_array($command, $this->safeCommands)) {
        return response()->json(
            $service
                ->vin($vin)
                ->{$command}()
        );
    }
    
    abort(404);
}

Here you can see I make a call to two methods from my service class: vin() and start(). The vin() method sets the value of the VIN in the class instance. Now look at the service class Vehicle::start().

public function start(): array
{
    $uri = sprintf('api/vehicles/v2/%s/engine/start', $this->vin);
    $response = $this->client->put($uri, [
        'headers' => $this->headers(),
    ]);
    
    return $this->decodedResponse($response);
}

Laravel's Dependency Injection takes care of the constructor and the required boot of the $client with the baseUrl and the headers. The constructor handles Ford's API authentication. Then, when I am ready to call start() the service just makes a put call to the appropriate URL from Ford.

Deployment

The bow in this project, at least to me, was the deployment. I had used Lambda functions in the past, but never for a whole PHP application. AWS Lambda function does not support the PHP out of the box. That is when I came across Bref, a really cool library to deploy PHP apps serverless.

The entire application is packaged and uploaded to AWS with a PHP binary, all configured with a simple yaml file. Bref takes most of the guess-work out of this deployment, and anytime I made an update I could run a simple console command and have my changes available within a couple of minutes.

$ php artisan deploy

Which just calls App\Console\Commands\Deploy::class.

public function handle()
{
    $this->comment(shell_exec('composer install --prefer-dist --optimize-autoloader --no-dev'));
    $this->comment(shell_exec('serverless deploy'));
}

The command would install the composer dependencies for production and call serverless deploy.

Security

The big question mark left would be security. While ideal, the goal for the project was to make the API as simple to implement as humanly possible. For Apple shortcuts to work, I needed something simple like hitting an Endpoint as a GET request and understanding the status of the response to make sure it was ok.

Finally, I didn't feel the need to implement any sort of authentication. The domain name would be a long random string generated by Amazon after deploying the app (Security Through Obscurity). Even if coincidentally anyone came across the URL https://rd2uwl2fsv.execute-api.us-east-1.amazonaws.com/api/vehicle/{vin}/start, it would do nothing more than start my car and get it warmed up for me šŸ˜Š.

In retrospect, a simple API Key authentication system would be an easy way to implement a new layer of security, while remaining compatible with my desired workflow. It would ensure my API can only be unlocked with the key. Lumen would make light work out of this since it has Authentication available out of the box with Laravel's robust authentication system. The compromise here was partially due to the lack of having a server. It would take spinning a database else where, which while is not a huge deal, I didn't particularly see a need for my use-case.

Roadmap

Currently, the Fordpass API logic in the repo is completely dead in the water. Ford's API is no longer available to third-party API integrations outside of what Ford explicitly allows. That means homebrewed apps like this one are simply not allowed. Ford has even banned accounts that have used the API through this unauthorized means.