Project Detroit: Lighting System
- Posted: May 21, 2012 at 9:00 AM
- 52,685 Views
- 4 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 give a detailed look into the external lighting system 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.
The external lighting system for Project Detroit is run on a web server on a Netduino Plus, which uses the .NET Micro Framework (NETMF). To get an accelerated start, we leveraged code from the CodePlex project Netduino Helpers to control the AdaFruit LPD8806 LED strip, and the web server base code from the Netduino forums. They have been tweaked slightly for our project.
We wanted the NETMF web server to have the exact same routes as the full-blown REST service layer in Detroit. With compiler conditionals, this allowed the same code to use the same routes on both the full .NET stack and NETMF. Since we couldn't use the ASP.NET MVC framework here, we had to make something close to it.
Controllers implement an interface with a method named ExecuteAction. When GetController is called, we return the correct controller but cast as the interface IController. This lets us figure out what controller should be executing the request and continue to work generically in the context of the request connection thread. If new functionality is needed, we create a new controller that inherits from IController and update GetController:
private static void RequestReceived(Request request)
{
// url comes in as /foo/bar/12345?style=255
//
// /CONTROLLER/ACTION/
//
// LIGHTING SAMPLE URL: /0/1/?r=255&g=255&b=255&zone=EnumAsInt
// /0/... 0 = NetduinoControllerType.Lighting
var validResponse = false;
try
{
Logger.WriteLine("Start: " + request.Url + " at " + DateTime.Now);
var urlInParts = request.Url.TrimStart(UriPathSeparator).Split(new[] {'?'}, 2);
string[] uriSubParts = null;
string[] queryStringSubParts = null;
if (urlInParts.Length > 0)
uriSubParts = urlInParts[0].Split(UriPathSeparator);
if (urlInParts.Length > 1)
queryStringSubParts = urlInParts[1].Split(QueryStringSeparator);
if (uriSubParts != null && uriSubParts.Length > 1)
{
var targetController = GetController(uriSubParts[0]);
if (targetController != null)
{
var action = (uriSubParts.Length >= 2) ? uriSubParts[1] : string.Empty;
var result = targetController.ExecuteAction(action, queryStringSubParts);
validResponse = true;
request.SendResponse(result.ToString());
Logger.WriteLine("Result: " + result + " from " + request.Url + " at " + DateTime.Now);
}
}
if (!validResponse)
{
SendIndexHtml(request);
}
}
catch(Exception ex0)
{
Logger.WriteLine(ex0.ToString());
}
}
NETMF can't parse an enum from a string, which is something that can be done in the full .NET Framework. But what does this actually mean? It means the routes for the NETMF web server are a bit unreadable. Here is the same route but with the parsing issue exposed:
Detroit's REST service route with ASP.NET MVC:
http://…/Lighting/Solid/?zone=all&r=255&g=0&b=0
Detroit's NETMF route:
http://…/0/3/?zone=0&r=255&g=0&b=0
This issue made direct device debugging a bit harder.
Parsing the route is broken into two parts. The first is determines what controller to target. For Project Detroit, that meant the lighting system or the rear glass system. Once we know the controller, we still need to parse the query string and action into something more useful:
#if NETMF
private static readonly char[] QueryStringParamSeparator = new[] { '=' };
public static LightingData ParseQueryStringValues(string action, params string[] queryStringParameters)
#else
public static LightingData ParseQueryStringValues(string action, Dictionary<string, object> queryStringParameters)
#endif
{
var data = new LightingData {LightingZone = LightingZoneType.All, LightingAction = ParseLightingActionType(action)};
#if NETMF
if (queryStringParameters != null)
{
foreach (var queryString in queryStringParameters)
{
var valueKey = queryString.Split(QueryStringParamSeparator);
if (valueKey.Length != 2)
continue;
var key = valueKey[0];
var value = valueKey[1];
#else
foreach(var key in queryStringParameters.Keys)
{
var value = queryStringParameters[key].ToString();
#endif
switch (key.ToLower())
{
case ZoneHuman:
case ZoneComputer:
data.LightingZone = ParseLightingZoneType(value);
break;
// more stuff
}
}
#if NETMF
}
#endif
// more stuff
return data;
}
Individually addressable LED strips allow us to tell each individual LED what color to be. To adjust the colors of the LED lights, we loop through all the LEDs, set their color value, and then call LedRefresh() on our LED strip. There's no need to set all of them, only the ones being updated:
private static void SolidAnimationWorker()
{
var leds = GetLedsToIlluminate();
var dataCopy = _data;
for (var i = 0; i < leds.Length; i++)
SetLed(leds[i], dataCopy.Red, dataCopy.Green, dataCopy.Blue);
LedRefresh();
}
A solid color is boring, but we can use procedural animations to add some flair. The current code supports the following animations:
Since we know the LED strip is a single strand, we can be clever with offsets and create groupings. By having a dedicated animation thread in each zone, we can have the grill of the car in Police Mode while the bottom fades to red and the vents fill to blue at different refresh rates.
To run an animation, we need to first kill the old animation thread, spin up a new thread, mark LEDs in the "All LED" zone as do not touch, and execute the new pattern. Without a way to mark the LEDs as "bad" in the "All" zone, the two animation threads would compete for setting the color and result in a flickering effect. Other zones don't have to worry about this since there is zero overlap of LEDs:
public bool ExecuteAction(string action, params string[] queryStringParameters)
{
var data = LightingDataHelper.ParseQueryStringValues(action, queryStringParameters);
SignalToEndAnimationThread(data.LightingZone);
int counter = 0;
while (counter++ < 5 && IsThreadAlive(data.LightingZone))
Thread.Sleep(10);
if (IsThreadAlive(data.LightingZone))
AbortAnimationThread(data.LightingZone);
// moving data bucket into public so new threads can leverage it
_data = data;
if (_data.LightingAction == LightingActionType.Police || _data.LightingAction == LightingActionType.Sweep)
{
_data.LightingZone = LightingZoneType.Grill;
if (IsThreadAlive(_data.LightingZone))
AbortAnimationThread(_data.LightingZone);
}
// cleaning up just incase
if (data.LightingZone != LightingZoneType.All)
{
for (var i = 0; i < _zoneAllAnimation.Length; i++)
{
var leds = GetLedsToIlluminate();
for (var z = 0; z < leds.Length; z++)
{
// flagging item as bad in animation lib
if (_zoneAllAnimation[i] == leds[z])
_zoneAllAnimation[i] = -1;
}
}
}
switch (_data.LightingAction)
{
case LightingActionType.Solid:
Debug.Print("Solid");
ExecuteThread(SolidAnimationWorker);
break;
// more patterns
}
return true;
}
We can now do something more interesting, like Police Mode:
private static void PoliceModeAnimationWorker()
{
var leds = GetLedsToIlluminate();
var dataCopy = _data;
var length = leds.Length;
var half = length / 2;
var index = 0;
while (IsThreadSignaledToBeAlive(dataCopy.LightingZone))
{
int redIntensity = (index == 0) ? 255 : 0;
int blueIntensity = (index == 0) ? 0 : 255;
for (var i = 0; i < half; i++)
SetLed(leds[i], redIntensity, 0, 0);
for (var i = half; i < length; i++)
SetLed(leds[i], 0, 0, blueIntensity);
LedRefresh();
index++;
index %= 2;
Thread.Sleep(100);
}
}
Hindsight is 20/20. Nothing is ever perfect and there are of course items we'd love to change. At the start, the goal was to make this fairly generic and not Project Detroit "aware." As you can see by looking at the source, that wasn't accomplished due to time constraints.
We would use a different LED system for two reasons: data transfer speed and power. The longer the LED strip is, the longer it takes to address an item further down the line. Project Detroit, having 15 meters of lighting in it, was impacted by this issue. It works well, just not as well as we hoped. The voltage requirements of the LEDs are another consideration. They are 5V, while a car uses 12V. Had we used 12V LEDs, our wiring would have been far simpler and wouldn't have required multiple transformers to get the required amperage needed at the correct voltage.
We think the lighting solution, coupled with the time and material constraints, worked out pretty well. Project Detroit was a ton of fun to build out and working with West Coast Customs was the chance of a lifetime.
![]()
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?
Really nice .............

Хуя себе я тоже так хочу программировать научиться !!!
good job
nice work .
netduino zindabaad(long live)
Remove this comment
Remove this thread
close