FordPass
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
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.
- Receive the voice input "Start my car"
- Go to the AWS Lambda function endpoint
- Parse response looking for
"status":200
- If not successful provide the necessary feedback.
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.