I am working on a Family Photos application that organizes digital pictures into a chronological structure.  I had created thrown together an application like this years ago, but it was no longer going to cut it, especially with the new Windows Home Server in the mix.  One key requirement for Family Photos is to very easily move pictures off of the SD card from the camera, queuing them up for organization, and allowing the SD card to be taken back and put back into the camera.  Our old process involved the following:

  1. Unlock the screen for Jeff's computer
  2. Plug the SD card into the SD->USB adapter
  3. Double-click an icon on the desktop (minimizing windows if needed)
  4. Watch the console window and wait for it to print a message stating that the program has finished moving all files off the drive
  5. Take the SD card out and be on your way

While a little clumsy, it had a high WAF.  Kelly knew how to do it, and it allowed her to be on her way in minutes, with a clear indication as to when she could take the card out.  Family Photos needs to offer the same benefits while ideally simplifying the process even further.

The HP MediaSmart Windows Home Server adds an interesting twist on this problem too: it's headless.  No monitor, keyboard or mouse.  That means there's no icon on the desktop, no mouse to click with anyway, and no console window to offer status indicators.  There's no user input and no output.  So how can we tell the server, "Hey, I just put in a drive that has photos.  Take the photos off of there as quickly as possible and let me know when you're done so that I can take the drive back.  Then, after I'm gone, organize all of the pictures for me.  Thanks!"  I haven't done any recreational coding since we moved, but this problem has been stirring in the back of my head for months.  I'm finally working on it, and it feels great.

It boils down to the two requirements: the server needs to accept input, and it needs to give output.  The rest of this article will focus on the output aspect, and we'll leave the input for another post.  We need to think hard about ways the home server can provide status indicators as its output.  Let's consider some options, ignoring the user input problem.

  1. Require a remote desktop session into the server to perform the task so that a console window can show status, just like the old process
  2. Send an email or IM when completed
  3. Send a network message to the family laptop, triggering it to pop up a dialog

None of those is good.  This is where I was stuck for a long time.  But the other night, I thought of option 4, and it was the clear winner.

  1. Find a way to programmatically flash the LEDs on the MediaSmart server

The MediaSmart server console provides a slider that adjusts the brightness of the LEDs for the drives.  The idea was to crank the brightness all the way up while the files were being moved over, and then dim the lights way back down when finished.  I'd tell Kelly, "When the lights basically turn off, you can take the card back out."  So, how can the brightness be controlled programmatically?  Let's ask Google.

Shiver me timbers!  An Easter Egg!?  That lets you perform light shows on your MediaSmart Server?  Go read this post and watch the video real quick to see what I’m talking about.

Now, that's just cool!  And boy does that offer up some new possibilities!  It turns out that the MediaSmart servers support an array of light themes, along with the variable brightness.  Instead of just changing the brightness, I can alter the light theme to give clues as to what the server is doing.

Specifically, when the process is copying files up from the USB stick, I'll have bright, red lights ascend over the USB drive.  When that's finished and the server begins doing some crazy processing of the photos, I'll have the server put on a holiday light show.  The dramatic change in theme will clearly indicate that the server is finished copying the files and it's off doing work--take the card out whenever you'd like.

It only took a minute to find that the LED settings are stored in the registry under HKLM.  And it was pretty straight-forward to determine the values of the themes and the brightness.  With this information in hand, the HomeServer.Notifications.Lights class is born.

   1: using System;
   2: using Microsoft.Win32;
   3:  
   4: namespace HomeServer.Notifications
   5: {
   6:   /// <summary>
   7:   /// Manipulate the lights on an HP MediaSmart Windows Home Server
   8:   /// </summary>
   9:   public class Lights : IDisposable
  10:     {
  11:   // Registry LocalMachine key path where the values are stored (as DWORD values)
  12:   private const string _key = @"SOFTWARE\Hewlett-Packard\HP MediaSmart Server\LEDs";
  13:  
  14:   // The registry key that we'll work against
  15:   private RegistryKey _leds;
  16:  
  17:   // Whether or not the key is currently opened for writing
  18:   private bool _isWriting;
  19:  
  20:   /// <summary>
  21:   /// Gets whether the lights are available on the current system.
  22:   /// </summary>
  23:   /// <remarks>
  24:   /// When unavailable, any attempt to read or write values will
  25:   /// result in an InvalidOperationException.
  26:   /// </remarks>
  27:   public bool Available
  28:         {
  29:             get
  30:             {
  31:   // Try to open the registry key for reading
  32:                 OpenForReading();
  33:  
  34:   // If we found the key, then indicate that the lights are available
  35:   return _leds != null;
  36:             }
  37:         }
  38:  
  39:   /// <summary>
  40:   /// Gets or Sets the native value for the brightness
  41:   /// </summary>
  42:   private object BrightnessValue
  43:         {
  44:             get
  45:             {
  46:   return Read("Brightness");
  47:             }
  48:             set
  49:             {
  50:                 Write("Brightness", value);
  51:             }
  52:         }
  53:  
  54:   /// <summary>
  55:   /// Gets or Sets the native value for the theme
  56:   /// </summary>
  57:   private object ThemeValue
  58:         {
  59:             get
  60:             {
  61:   return Read("Theme");
  62:             }
  63:             set
  64:             {
  65:                 Write("Theme", value);
  66:             }
  67:         }
  68:  
  69:   /// <summary>
  70:   /// The current brightness value: 0-9
  71:   /// </summary>
  72:   public Int32 CurrentBrightness
  73:         {
  74:             get { return Convert.ToInt32(BrightnessValue); }
  75:             set
  76:             {
  77:   if (value < 0 || value > 9)
  78:                 {
  79:   throw new ArgumentOutOfRangeException("Brightness values must be 0-9");
  80:                 }
  81:  
  82:                 BrightnessValue = value;
  83:             }
  84:         }
  85:  
  86:   /// <summary>
  87:   /// The current Theme of the lights
  88:   /// </summary>
  89:   public Theme CurrentTheme
  90:         {
  91:             get { return (Theme)Convert.ToInt32(ThemeValue); }
  92:             set { ThemeValue = (Int32)value; }
  93:         }
  94:  
  95:   /// <summary>
  96:   /// Open the registry key for reading
  97:   /// </summary>
  98:   /// <remarks>
  99:   /// Store the open key in a member variable, and don't re-open
 100:   /// the key if it's already open
 101:   /// </remarks>
 102:   private void OpenForReading()
 103:         {
 104:   if (_leds == null)
 105:             {
 106:                 _leds = Registry.LocalMachine.OpenSubKey(_key, false);
 107:             }
 108:         }
 109:  
 110:   /// <summary>
 111:   /// Read a native value from the registry key
 112:   /// </summary>
 113:   /// <remarks>
 114:   /// Will ensure the lights are available and then open the key
 115:   /// for reading in order to get the value
 116:   /// </remarks>
 117:   /// <param name="valueName"></param>
 118:   /// <returns></returns>
 119:   private object Read(string valueName)
 120:         {
 121:             EnsureAvailable();
 122:             OpenForReading();
 123:  
 124:   return _leds.GetValue(valueName);
 125:         }
 126:  
 127:   /// <summary>
 128:   /// Write a native key value
 129:   /// </summary>
 130:   /// <remarks>
 131:   /// Will ensure the lights are available and that the key
 132:   /// is presently opened for writing.  If it was previously
 133:   /// opened for reading, it will be closed and then re-opened
 134:   /// for writing.
 135:   /// </remarks>
 136:   /// <param name="valueName"></param>
 137:   /// <param name="value"></param>
 138:   private void Write(string valueName, object value)
 139:         {
 140:   // Make sure the lights are available
 141:             EnsureAvailable();
 142:  
 143:   // If we already had the key open for reading, close it
 144:   if (_leds != null && !_isWriting)
 145:             {
 146:                 _leds.Close();
 147:                 _leds = null;
 148:             }
 149:  
 150:   // If we don't have the key open, open it for writing
 151:   if (_leds == null)
 152:             {
 153:                 _leds = Registry.LocalMachine.OpenSubKey(_key, true);
 154:                 _isWriting = true;
 155:             }
 156:  
 157:   // Set the value and flush to disk
 158:             _leds.SetValue(valueName, value);
 159:             _leds.Flush();
 160:         }
 161:  
 162:   /// <summary>
 163:   /// Ensure the lights are available.  When unavailable, throw
 164:   /// InvalidOperationException.
 165:   /// </summary>
 166:   private void EnsureAvailable()
 167:         {
 168:   if (!Available)
 169:             {
 170:   throw new InvalidOperationException("Lights not available.  Maybe this isn't a MediaSmart server.");
 171:             }
 172:         }
 173:  
 174:   /// <summary>
 175:   /// Implement IDisposable, properly disposing of resources
 176:   /// </summary>
 177:   public void Dispose()
 178:         {
 179:   // Close the registry key if still open
 180:   if (_leds != null)
 181:             {
 182:                 _leds.Close();
 183:                 _leds = null;
 184:             }
 185:         }
 186:  
 187:   /// <summary>
 188:   /// Offer some helper values for brightness, allowing for consistent usage
 189:   /// </summary>
 190:   /// <remarks>
 191:   /// This is a static class with these readonly properties instead of an enum
 192:   /// because an enum would have required constant casting.
 193:   /// </remarks>
 194:   public static class Brightness
 195:         {
 196:   public static readonly Int32 Off = 0;
 197:   public static readonly Int32 Low = 1;
 198:   public static readonly Int32 MediumLow = 3;
 199:   public static readonly Int32 Medium = 4;
 200:   public static readonly Int32 MediumHigh = 5;
 201:   public static readonly Int32 High = 7;
 202:   public static readonly Int32 Max = 9;
 203:         }
 204:  
 205:   /// <summary>
 206:   /// The list of themes supported by the server
 207:   /// </summary>
 208:   public enum Theme
 209:         {
 210:             Default = 0,
 211:             HolidayLights = 1,
 212:             DescendingChaserBlue = 2,
 213:             AscendingChaserBlue = 3,
 214:             DescendingChaserPurple = 4,
 215:             AscendingChaserPurple = 5,
 216:             DescendingChaserRed = 6,
 217:             AscendingChaserRed = 7,
 218:             PulsingColors = 8,
 219:             PulsingBlue = 9,
 220:             PulsingPurple = 10,
 221:             PulsingRed = 11,
 222:             PulsingSystemColors = 12,
 223:             NightRiderBlue = 13,
 224:             NightRiderPurple = 14,
 225:             NightRiderRed = 15,
 226:             MorseCodeCredits = 16
 227:         }
 228:     }
 229: }


The usage is pretty straight-forward.

   1: if (lights.Available)
   2: {
   3:     lights.CurrentTheme = HomeServer.Notifications.Lights.Theme.AscendingChaserRed;
   4:     lights.CurrentBrightness = HomeServer.Notifications.Lights.Brightness.Max;
   5: }


The pattern I'm finding myself using is recording the original brightness and theme at the beginning of a process, then manipulate the lights during the process, and restore the lights afterward.  I will be adding a more task-based API to the class, to support methods such as:

   1: lights.StoreSettings();
   2: lights.Show(Theme.HolidayLights, Brightness.Max);
   3: // Do work here
   4: lights.RestoreSettings();


I foresee using this class for other Home Server processes I end up writing, so that the server can report its status, and I can glean it from the couch.  It'll be a balancing act between informative and annoying.  We'll have to see what the WAF factor is on this one!