Project Detroit: An Overview
- Posted: May 14, 2012 at 9:00 AM
- 50,915 Views
- 11 Comments
Loading User Information from Channel 9
Something went wrong getting user information from Channel 9
Loading User Information from MSDN
Something went wrong getting user information from MSDN
Loading Visual Studio Achievements
Something went wrong getting the Visual Studio Achievements
In this article, we will give an overview of the technical side of Project Detroit, the Microsoft-West Coast Custom Mustang creation. If you're not already familiar with this project, you can find more information here.
It’s important to keep in mind that this car was built for a TV show with a set schedule. As a result, there are a number of unique design decisions that came into play.
![]()
Schedule
Working backwards, the reveal for the car was set for Monday November 28, 2011 at the Microsoft Store in Bellevue, Washington. We started the project in early August, which gave us approximately 12 weeks for research, development, vehicle assembly, and testing. This was by far the #1 design decision as any ideas or features for the car had to be implemented by the reveal date.
Off the Shelf Parts
Another key design decision was to, where possible, use off-the-shelf hardware and software in order to allow interested developers to build and reuse some of the subsystems for their own car (at least the ones that don’t require welding). For example, instead of buying pricey custom sized displays for the instrument cluster or passenger display, we used stock Samsung Series 7 Slate PCs and had West Coast Customs do the hard work of building a custom dash to hold the PC.
The car is packed with a variety of computers and networking hardware.
Note: One of the limitations of the Kinect SDK is that if you have multiple Kinects plugged into one PC, only one of those Kinects can do skeletal tracking at a time (color/depth data works just fine). Because of this, we decided to have a dedicated laptop plugged into the front Kinect and another laptop plugged into the back Kinect in order to allow front and back skeletal tracking at the same time. If we'd not used simultaneous skeletal tracking, we could have combined all of the systems onto a single laptop.
Here is a quick overview of the application architecture.
The REST Service Layer allowed different systems talk to one another. More importantly, it allowed different services to control hardware they normally wouldn't be able to access.
We have already released an article and library on the OBD-II portion of the car. In short, OBD-II stands for On-Board Diagnostics. Hooking into this port allows one to query for different types of data from the car, which we use to get the current speed, RPMs, fuel level, etc. for display in the Instrument Cluster and other locations. OBD can do far more than this, but it's all we needed for our project. Please see the linked articles for further details on the OBD-II library itself.
For the car, because only one application can open and communicate with a serial port at one time, we created a WCF service that polls the OBD-II data from the car and GPS data from a Microsoft Streets & Trips GPS locator, and returns it to any application that queries the service.
For the OBD library, we used a manual connection to poll different values at different intervals. For values critical to driving the car—like RPM, speed, etc.—we polled for the values as quickly as the car could return them. With other values that weren’t critical to driving the car—like the fuel level, engine coolant temperature, etc.—we polled at a 1-2 second interval. For GPS, we subscribed to the LocationChanged event, which would fire when the GPS values changed.
Rather than creating a new serial port connection for every WCF request for OBD data, we created a singleton service that is instantiated when the service first runs. Accordingly, there is only one object in the WCF service that represents the last OBD and GPS data returned, which is obtained by the continual reading of the latest OBD data using the OBD library as described above. This means that calls to the WCF service ReadMeasurement method didn’t actually compute anything, but instead serialized the last saved data and returned it via the WCF service.
Since WCF supports multiple protocols, we implemented HTTP and TCP and ensured that any WCF service options we chose worked on Windows Phone, which, for example, can only use basic HTTP bindings.
To enable the ability to change the programming model later and to simplify the polling of the service, we built a helper library for Windows and Windows Phone that abstracts all the WCF calls.
The code below creates a new ObdService class and signs up for an event when the measurement has changed. The Start method does a couple of things: it lets you set the interval that you want to poll the ObdService, in this case every second (while the instrument cluster needs fast polling, the database logger can poll once a second). It also determines what IP address the service is hosted at (localhost), the protocol (HTTP or TCP), and whether to send “demo mode” data. Since one of the main ways the car is showcased is when it’s stopped on display, “demo mode” sends fake data, instead of always returning 0's for MPH, RPM, etc., so people can see what the instrument cluster would look like in action.
_service = new ObdService();
_service.ObdMeasurementChanged += service_ObdMeasurementChanged;
_service.Start(new TimeSpan(0, 0, 0, 0, 1000), localhost, Protocol.Http, false);
void service_ObdMeasurementChanged(object sender, ObdMeasurementChangedEventArgs e)
{
Debug.Writeline("MPH=” + e.Measurement.MilesPerHour);
}
To record and capture the car telemetry data like MPH, RPM, engine load, and throttle (accelerator) position, as well as location data (latitude, longitude, altitude, and course), we used a SQL Server Express database with a simple, flat Entity Framework model, shown below. The primary key, the ObdMeasurementID is a GUID that is returned via the ObdService. Just like above, the database logger subscribes to the ObdMeasurementChanged event and receives a new reading at the time interval set in the Start() method.

The Windows Azure data model uses Azure Table Services instead of SQL Server. The data mapping is essentially the same since both have a flat schema.
For Azure Table Storage, in addition to the schema above, you also need a partition key and a row key. For the partition key, we used a custom TripID (GUID) to represent a Trip. When the car is turned on/off a new TripID is created. That way we could group all measurements for that particular trip and do calculations based on that trip, like the average miles per gallon, distance traveled, fastest speed, etc. For the row key, we used a DateTimeOffset and a custom extension method, ToEndOfDays() that provides a unique numerical string (since Azure's row key is a string type) that subtracts the time from the DateTime.Max value. The result is that the earlier a DateTime value, the larger the number.
Example:
Time=5/11/2012 9:14:09 AM, EndOfDays=2520655479509478223 //larger
Time=5/11/2012 9:14:11 AM, EndOfDays=2520655479482804811 //smaller
Since they are ordered in reverse order, with the most recent date/time being the first row, we can write an efficient query to pull just the first row to get the current latitude/longitude without needing to scan the entire table for the last measurment.
public override string RowKey { get { return new DateTimeOffset(TimeStamp).ToEndOfDays(); } set { //do nothing } } public static class DateTimeExtensions { public static string ToEndOfDays(this DateTimeOffset source) { TimeSpan timeUntilTheEnd = DateTimeOffset.MaxValue.Subtract(source); return timeUntilTheEnd.Ticks.ToString(); } public static DateTimeOffset FromEndOfDays(this String daysToEnd) { TimeSpan timeFromTheEnd = newTimeSpan(Int64.Parse(daysToEnd)); DateTimeOffset source = DateTimeOffset.MaxValue.Date.Subtract(timeFromTheEnd); return source; } }
To upload data to Azure, we used a timer-based background uploader that would check to see if there was an internet connection, and then filter and upload all of the local SQL Express rows that had not been submitted to Azure using the Submitted boolean database field. On the Azure side, we used an ASP.NET MVC controller to submit data. The controller deserializes the data into a List<MeasurementForTransfer> type, it adds the data to a blob, and adds the blob to a queue as shown below.
A worker role (or many) will then read items off the queue and the new OBD measurement rows are placed into Azure Table Storage.
public ActionResult PostData() { try { StreamReader incomingData = new StreamReader(HttpContext.Request.InputStream); string data = incomingData.ReadToEnd(); JavaScriptSerializer oSerializer = new JavaScriptSerializer(); List<MeasurementForTransfer> measurements; measurements = oSerializer.Deserialize(data, typeof(List<MeasurementForTransfer>)) as List<MeasurementForTransfer>; if (measurements != null) { CloudBlob blob = _blob.UploadStringToIncoming(data); _queue.PushMessageToPostQueue(blob.Uri.ToString()); return new HttpStatusCodeResult(200); } ... } }
Much of this is also covered in our previously released OBD-II library where the instrument cluster application is included as a sample. This is a WPF application that runs on a Windows 7 slate. It contains three different skins designed by 352 Media—a 2012 Mustang dashboard, a 1967 Mustang dashboard, and a Metro-style dashboard—each of which can be "swiped" through. This application queries the OBD-II WCF service described above as quickly as it can to retrieve speed, RPM, fuel level, and other data for display to the driver. The gauges are updated in real-time just as a real dashboard instrument cluster would behave.
The HUD (or Heads Up Display) application runs on one of the two Windows 7 computers in the car. This is a full-screen application that is output via a projector to a series of mirrors and a projection screen. This is then reflected onto the front glass of the windshield of the car. To install these, we altered the physical car's body and created brackets to mount mirrors and the projectors. In the picture on the left, you can see the dashboard's structural member pivoted outward. You can see the 12" section we removed and added in the base plate to allow light to be reflected through to the windshield. Bill Steele helped design and implement the physical HUD aspect into the car.
The HUD application has several different modes. The mode is selected from the Windows Phone application.
One of the main ways to control the vehicle is through the Windows Phone application. ![]()
The first pivot of the app allows the user to lock, unlock, start the car, and set off the alarm. This is done through the Viper product from Directed Electronics.
The second pivot contains the remaining ways that a user can interact with the car.
The passenger interface runs on a Samsung Series 7 slate running the Windows 8 Consumer Preview. This interface has a subset of the functionality provided by the Windows Phone application, but communicates through the same REST service. From this interface, the passenger can set the car horn sound effect, view the front and back Kinect cameras, select a Point of Interest category to be displayed on the HUD, and select the image, video or message that will be displayed on the rear window.
The external lighting system was controlled by a web server running on a Netduino Plus using a Sparkfun protoshield board to simplify wiring, and allow for another shield to be used. The actual lights were Digital Addressable RGB LED w/ PWM. We'll also have a more in-depth article on this system on Coding4Fun shortly.
The car is broken down into different zones—grill, wheels, vents, etc. It also has a bunch of pre-defined procedural animation patterns that have a few adjustable parameters that allow for things like a snake effect, a sensor sweep, or even a police pattern. Each zone has its own thread which provides the ability to have multiple animation patterns going at the same time. When a command is received, the color, pattern, zone, and other data is then processed.
Here is a basic animation loop pattern.
private static void RandomAnimationWorker()
{
var leds = GetLedsToIlluminate();
var dataCopy = _data;
var r = new Random();
while (IsThreadSignaledToBeAlive(dataCopy.LightingZone))
{
for (var i = 0; i < leds.Length; i++)
SetLed(leds[i], r.Next(255), r.Next(255), r.Next(255));
LedRefresh();
Thread.Sleep(dataCopy.TickDuration);
}
}
The rear projection system consists of two 4” linear actuators, a linear actual controller, the NETMF web server from above, a Seeed Studio Relay Shield, the back glass of a 1967 Ford Mustang, some rear projection film, a low profile yet insanely bright projector that accepts serial port commands, and a standard USB to serial adapter.
The REST service layer toggles the input of the projector based on the selected state. This would allow us to go from the HDMI output of an Xbox 360 to the VGA output of the laptop. While doing this, the REST layer sends a command to the NETMF web server to either raise or lower the actuators.
Here is the code for the NETMF to control the raising and lowering the glass:
public static class Relay
{
// code for for Electronic Brick Relay Shield
static readonly OutputPort RaisePort = new OutputPort(Pins.GPIO_PIN_D5, false);
static readonly OutputPort LowerPort = new OutputPort(Pins.GPIO_PIN_D4, false);
const int OpenCloseDelay = 1000;
public static bool Raise()
{
return ExecuteRelay(RaisePort);
}
public static bool Lower()
{
return ExecuteRelay(LowerPort);
}
private static bool ExecuteRelay(OutputPort port)
{
port.Write(true);
Thread.Sleep(OpenCloseDelay);
port.Write(false);
return true;
}
}
This is a WPF application that leveraged the file system on the computer to communicate between the REST service layer and itself. Visually, it shows the message/image/video in the rear view mirror but it actually does two other tasks, it operates our car horn system and plays the recorded audio output from the phone.
To interact with people, we installed an external audio PA or Public Address system. This system is hooked into the laptop that is connected to the car horn, and can play audio data from the phone. Having a PA system that is as simple as an audio jack that plugs into a PC enabled us to have different ringtones for the car horn and to talk through the car using Windows Phone.
After months of planning and building, the Project Detroit car was shown to the world on an episode of Inside West Coast Customs. Though it was a ton of work, the end product is something we are all proud of. We hope that this project inspires other developers to think outside the box and realize what can be done with some off-the-shelf hardware, software, and passion.
Comments have been closed since this content was published more than 30 days ago, but if you'd like to continue the conversation,
please create a new thread in our Forums,
or
Contact Us and let us know.
Follow the Discussion
Oops, something didn't work.
What does this mean?
Following an item on Channel 9 allows you to watch for new content and comments that you are interested in. You need to be signed in to Channel 9 to use this feature.What does this mean?
Following an item on Channel 9 allows you to watch for new content and comments that you are interested in and view them all on your notifications page.sign up for email notifications?
Ah, information about HUD. Thank you very much
Ok,.. and how can I get the car now?
Video of the car running & all the good stuff inside being tested?
know where i can watch this online? ive cut the cable cord a while ago
@cents: right now I don't believe Inside West Coast Customs is streamed to the internet
@Jesus D: Check out the episode
Is Primary Focal Mirror just surface mirror or concave mirror? Where can I buy this one?
Now if this showed up at my local dealership....I may just have to take it for a test drive!
@nesher: optics all depend on how you have your set up.
@Clint:Just trying to understand how it works and what is the difference in image between concave and flat mirror.
And where have you bought the mirror?
Remove this comment
Remove this thread
close