Project Detroit: Lighting System

EDN Admin

Well-known member
Joined
Aug 7, 2010
Messages
12,794
Location
In the Machine
In this article, we give a detailed look into the external lighting system of Project Detroit, the Microsoft-West Coast Custom Mustang creation. If youre not already familiar with this project, you can find more http://channel9.msdn.com/Blogs/Vector/The-400-horsepower-device information here . http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/netduino%5B8%5D.jpg <img title="netduino" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/netduino_thumb%5B3%5D.jpg" alt="netduino" width="500" height="375" border="0 Overview 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 http://netduinohelpers.codeplex.com/ Netduino Helpers to control the http://www.adafruit.com/products/306 AdaFruit LPD8806 LED strip, and the http://forums.netduino.com/index.php?/topic/575-updated-web-server/ web server base code from the Netduino forums . They have been tweaked slightly for our project. Controllers, Routes, RESTful oh my! 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 couldnt use the ASP.NET MVC framework here, we had to make something close to it. Controllers 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: <pre class="brush: csharp
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());
}
}
[/code] Routes, strings and enums on NETMF NETMF cant 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: Detroits REST service route with ASP.NET MVC:
http://…/Lighting/Solid/?zone=all&r=255&g=0&b=0

Detroits NETMF route:
http://…/0/3/?zone=0&r=255&g=0&b=0 This issue made direct device debugging a bit harder. Parsing the Action and query string 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: <pre class="brush: csharp
#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;
}
[/code] Turning on the lights 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. Theres no need to set all of them, only the ones being updated: <pre class="brush: csharp
private static void SolidAnimationWorker()
{
var leds = GetLedsToIlluminate();
var dataCopy = _data;
for (var i = 0; i < leds.Length; i++)
SetLed(leds, dataCopy.Red, dataCopy.Green, dataCopy.Blue);
LedRefresh();
}
[/code] Zones / Animation Threads A solid color is boring, but we can use procedural animations to add some flair. The current code supports the following animations: Solid - A single solid color. Fill - Goes from black to the desired color. The color stays lit. Much like filling a glass of liquid. <img src=http://ecn.channel9.msdn.com/o9/content/images/emoticons/emotion-1.gif?v=c9 alt=Smiley /> Random - LEDs are set to a random value. Pulse - A glowing effect from bright to black. Snake - A one way lighting effect with a fading tail that moves left to right. Sweep - This effect was inspired by KITT and a Cylon robot and is similar to a snake pattern. The effect can also handle being split in two while maintaining a continual line for items such as the grill or the wheels. Police - Each side flashes a red and blue light. 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 dont have to worry about this since there is zero overlap of LEDs: <pre class="brush: csharp
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 == leds[z])
_zoneAllAnimation = -1;
}
}
}
switch (_data.LightingAction)
{
case LightingActionType.Solid:
Debug.Print("Solid");
ExecuteThread(SolidAnimationWorker);
break;
// more patterns
}
return true;
}
[/code] We can now do something more interesting, like Police Mode: <pre class="brush: csharp
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, redIntensity, 0, 0);
for (var i = half; i < length; i++)
SetLed(leds, 0, 0, blueIntensity);
LedRefresh();
index++;
index %= 2;
Thread.Sleep(100);
}
}
[/code] http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/WP_000197%5B5%5D.jpg <img title="WP_000197" src="http://files.channel9.msdn.com/wlwimages/1932b237046e4743a4e79e6800c0220f/WP_000197_thumb%5B2%5D.jpg" alt="WP_000197" width="500" height="375" border="0 Improvements Hindsight is 20/20. Nothing is ever perfect and there are of course items wed 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 wasnt 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 wouldnt have required multiple transformers to get the required amperage needed at the correct voltage. Conclusion 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. <img src="http://files.channel9.msdn.com/thumbnail/09615539-1a33-49f7-b438-ca1b9543d712.jpg" alt="" width="500" height="284 <img src="http://m.webtrends.com/dcs1wotjh10000w0irc493s0e_6x1g/njs.gif?dcssip=channel9.msdn.com&dcsuri=http://channel9.msdn.com/Feeds/RSS&WT.dl=0&WT.entryid=Entry:RSSView:f6bef172711342dbbca9a046016a6843

View the full article
 
Back
Top