Netduino: Ordering Off the Menu

The source code for this article can be downloaded here: LightControllerWithMenu.zip.

This is the 5th in a series of projects for the Netduino and Seeed Studio’s Grove Starter Bundle.

The first four articles in the series were:

  1. Netduino Enters the Grove
  2. Netduino: Time and Weather
  3. Netduino: Let There Be Light
  4. Netduino: Here Comes The Night

Today, my friends, the voice of the people has been heard, and the reign of an oppressive dictator shall finally end.

For too long (about a month-and-a-half, to be specific, since my Netduino: Time and Weather post), we’ve had only one way to enter the date and time and other settings: the user-hostile serial connection. It forced us to drag our notebooks over to the Netduino, open serial terminals, enter obscure commands, and other things too loathsome to mention. Is this any way to live?

The people want freedom. The freedom to just walk up to a Netduino and set the date and time at the click of a button. The freedom to choose Celcius or Fahrenheit, at the click of a button.  (Though only imperialists use Fahrenheit, you know.) The freedom to turn off the LCD backlight – well, you get the idea.

In short, the people want a menu system.

This new menu system will follow the orders of one person only: you the user. Well, technically, also me, the programmer. And if you choose to fix my bugs customize my code, then also you (again), the programmer. Oh, and the reviled serial connection is going to remain in, um, a strictly unofficial capacity to ensure an orderly transition.

Pin Assignments

The pin assignments are unchanged from the previous blog post, Netduino: Here Comes The Night. The changes in this installment of the project are purely software.

User Interface

To invoke the snazzy new menu system, take the Red pill — I mean, press the Red button. (For those who aren’t using the Seeed Grove hardware system, the red button is connected to D7, and the green to D8. In Grove terminology, this is a button “twig” connected to the D7/D8 plug on the Grove stem).

Throughout the menu system, the buttons are used as follows (aaah – a user manual!):

  • Red button click – scroll down
  • Green button click – scroll up
  • Red button double-click – select a menu item
  • Green button double-click – exit the current menu

Some of the menus allow you to change a numeric value, like the hour of the day. In these menus:

  • Red button click – increase the value by 1
  • Green button click- decrease the value by 1
  • Red or Green button press and hold – repeatedly increase or decrease the value of the number
  • Red button double-click – save the current value of the number and exit the current menu
  • Green button double-click – exit the current menu without saving

Programmer Show And Tell

The heart of this update to the project, and the part you are most likely to want to change, is the menu configuration. I didn’t want to reinvent the wheel, so I carefully looked through the work that had already been done on creating a menu system for .Net Micro Framework devices.  When I couldn’t find what I wanted on the first page of the Google results, I got annoyed and reinvented the wheel.

Near the top of Program.cs, you’ll find the following array definition:

static string[] strMenuItems = new string[] {
"1", "Date `yy`-`MM`-`dd`", "1-1",
"1", "Time `HH`:`mm`", "2-1",
"1", "Timers", "3-1",
"1", "Settings", "4-1",
"1-1", "Year `yy`", "_UD-`yy`",
"1-1", "Month `MM`", "_UD-`MM`",
"1-1", "Day `dd`", "_UD-`dd`",
"2-1", "Hour `HH`", "_UD-`HH`",
"2-1", "Min `mm`", "_UD-`mm`",
"3-1", "Light On `HH:mm+`", "3-2",
"3-1", "Light Off `HH:mm-`", "3-3",
"3-2", "Manual `HH:mm+`", "3-2-1",
"3-2", "Sunset `HH:mm_`", "_SUNSET",
"3-3", "Manual `HH:mm-`", "3-3-1",
"3-3", "Sunrise `HH:mm*`", "_SUNRISE",
"3-2-1", "Hour `HH+`", "_UD-`HH+`",
"3-2-1", "Min `mm+`", "_UD-`mm+`",
"3-3-1", "Hour `HH-`", "_UD-`HH-`",
"3-3-1", "Min `mm-`", "_UD-`mm-`",
"4-1", "Temp Fmt `TD`", "4-1-1",
"4-1", "Backlight `LB`", "_BACKLITE",
"4-1", "Brightness `BR`", "_UD-`BR`",
"4-1-1", "Celcius", "_CELCIUS",
"4-1-1", "Fahrenheit", "_FAHR"
};

One thing you’ll notice right away is that this should be a 2 dimensional array (i.e. string[,]), but isn’t. Oddly, there is no such thing as a 2-dimensional array in the .Net Micro Framework. Instead, you can have arrays of arrays (e.g. string[][] strMenuItems = new string[20][], where the 2nd-level array can be of a different size in each element of the main array. This weirded me out, so I went with a 1-dimensional array instead.

Each menu item consists of 3 strings:
- A menu ID – this must be the same for each item in that level of the menu
- A caption, which can contain embedded keywords delimited with the backquote character (`)
- The result of selecting (double-clicking) that menu item, which can be the ID of a submenu to display, or an action (actions are prefixed with an underscore).

So, for example, the first level menu has 4 items that appear on the LCD like this:

Date 11-02-14
Time 18:05
Timers
Tools

The first 2 items contain keywords that are replaced at run-time with the current date and time.
Selecting the first item in this menu displays sub-menu “1-1”, which looks like

Year 11
Month 02
Day 14

Selecting any of these menus selects an action, such as “_UD-`yy`” — this displays an “Up/Down” menu for setting the year. More on that below.

I’ve added a new class to the project, clsLCDMenu, and given it sole responsibility for displaying the menu and handling all button input. This class is intended to be a generalized menu handler: it knows  how to display lines of a menu and allow the user to navigate through the menu using the buttons, but it knows nothing about how to handle the actions selected by the user.

This design results in a fairly extendable and reusable menu. There are no hard-coded limits on the # of elements in a menu of the # of levels of submenus. To add new menu items, or create an entirely different menu system, I won’t have to change any of the code in clsLCDMenu. Instead, I just change the strMenuItems array, above, and add some “action handler” logic to some callback methods in Program.cs, described below.

Since clsLCDMenu “owns” the LCD and buttons when the menu is up, I have to make sure that the existing code never tries to write to the LCD or respond to a button click when the menu is up. I do this by turning off the timers and “unhooking” the button event handlers, as follows:

static void launchMenu()
{
  // turn off the timers that update the display
  timerTemperature.Dispose();
 timerTime.Dispose();

  //unhook the button event handlers
  buttonRed.OnInterrupt -= buttonRed_OnInterrupt;
  buttonGreen.OnInterrupt -= buttonGreen_OnInterrupt;

  // buttonGreen = up, buttonRed = down
  menu = new clsLCDMenu(strMenuItems, lcd, buttonGreen, buttonRed, getStringForMenu, getValuesForMenu,     setValueFromMenu);
}

The “-=” statement that unhooks the event handlers will look freaky to anyone who hasn’t encountered it in .Net before, since neither side of the “-=” is a numeric value. This syntax removes the main class’s button event handlers from a virtual “list” of handlers for the event. It isn’t all that commonly used in .Net, since event handlers are automatically “unhooked” when the class that contains the event handlers is destroyed, such as when a form is closed.

The constructor for the clsLCDMenu class looks like this:

public clsLCDMenu(string[] strItems, ILCD lcd, InterruptPort buttonUp, InterruptPort buttonDown,
MenuCallback getCaption, MenuGetValue getValue, MenuSetValue setValue)

The caller passes a reference to the LCD and “up” (green) and “down” (red) buttons, along with 3 callback methods. The callback methods add the “business logic” to the menu system, if you will: they fill in the keywords in the menu captions, and handle the menu actions. Specifically, there are 3 types of callbacks:

  • MenuCallback – gets passed a menu caption containing keywords (e.g. “Light On `HH:mm+`”), and passes back the caption to be displayed to the user (e.g. Light On 20:30”.
  • MenuGetValue – gets passed an “up/down” menu definition (e.g. “_UD-`MM`”) and passes back the current value, minimum value and maximum value to be used for the up/down menu.
  • MenuSetValue – gets passed an action that needs to be taken: either the changed value from an up/down menu (e.g. the user has changed the hour setting on the clock), or some other action such as “_SUNSET” (the user has set the “Light On” timer to sunset.)

Callback methods in .Net require 2 things:
1) A “delegate” definition. For example, the definitions of the 3 callback methods used by clsLCDMenu are:

public delegate string MenuCallback(string s);
public delegate bool MenuGetValue(string menuType, out Int16 start, out Int16 min, out Int16 max, out byte increment);
public delegate void MenuSetValue(string menuType, Int16 value);

2) The actual method to be executed when the callback is invoked. References to these 3 methods are passed to the clsLCDMenu constructor by the launchMenu() method, shown earlier. The constructor saves these references in variables of the “delegate” type. For example, the MenuCallback is saved by clsLCDMenu as:

private MenuCallback menuGetCaption;

…

public clsLCDMenu(string[] strItems, ILCD lcd, InterruptPort buttonUp, InterruptPort buttonDown,
MenuCallback getCaption, MenuGetValue getValue, MenuSetValue setValue)

…

this.menuGetCaption = getCaption;

When clsLCDMenu needs to call one of the callback methods, it uses its reference variable, calling it as if it was a method. For example:

string strMenuText = menuGetCaption(((menuItem)lstMenu[intMenu]).StrCaption);

I won’t give an explanation of what the callback methods do. They are actually quite straightforward: just a bunch of “if” and “switch” statements that examine the parms to figure out what kind of data they are dealing with, then returning the corresponding settings or taking the corresponding action.

Two other features that are worth explaining, though, are the buttons’ double-click and auto-repeat handlers. These both rely on timers, which are a pleasure to work with in .Net compared to the alternatives that you need to hack together in other microcontroller platforms.

The double-click is a click followed by another click (duh). As every new (or elderly) PC user learns the hard way, the difference between a double-click and 2 single clicks is the amount of time in between.

So, the code which handles the first click sets a timer. If the timer goes off before another click occurs, then that was a single click, and the code handles it accordingly. If a second click occurs before the timer expires, that’s a double click.

The code which sets the double-click timer is:

timerButtonDown = new Timer(new TimerCallback(buttonClick), "Down", DOUBLECLICK_DELAY, -1);

The DOUBLECLICK_DELAY is a constant, in milliseconds, which determines how fast Granny has to move to pull off a double-click. I ended up setting it to 400 ms, which was short enough that I didn’t accidentally double-click when I wanted to make 2 separate clicks.

The “buttonClick” timer event handler now contains the logic that was previously in the button interrupt event: a click isn’t a click until the timer goes off:

private void buttonClick(object data)
{
  if ((string)data == "Down")
  {
    // cancel the button click timer
    if (timerButtonDown == null)
    {
      // oops, someone already cancelled it, so we should ignore it
      return;
    }
    timerButtonDown.Dispose();
    timerButtonDown = null;
    blnButtonDownClicked = false; // no longer waiting for double-click
    goDown();

You’ll notice some seemingly unnecessary null handling.  Here’s the thing…

If the user clicks the button a second time, then we want to cancel this timer event: it’s a double-click action, not a single-click. In theory, disposing a timer should cancel the timer event. However, when testing that code I found that double-clicking would often result in both the double-click and single-click action being performed, since the timer event wasn’t cancelled by disposing the timer object. Setting the timer to null didn’t cancel the event either.

So, I ended up adding code to the timer event to see if the timer had been set to null, and aborting the event if it had. Crude, but effective, and a lot more reliable than polling the button a la Arduino.

To implement button auto-repeat, I used the button “push-and-hold” timer (as described in the Netduino: Let There Be Light article), and had it re-invoke the timer if the button was still being held down:

private void buttonHold(object data)
{
  if ((string)data == "Down")
  {
  …
  if (!buttonDown.Read())
  {
    // not being held down anymore
    return;
  }
  intDownRepeatCount += 1;
  int delay = BUTTONREPEAT_DELAY;
  if (intDownRepeatCount >= 4)
    delay = TURBOREPEAT_DELAY;
    timerButtonDown = new Timer(new TimerCallback(buttonHold), "Down", delay, -1);

As you can see, I implemented auto-repeat the same way as most alarm clocks: the number advances relatively slowly at first (BUTTONREPEAT_DELAY is 500 ms), then more quickly after a couple of seconds (TURBOREPEAT_DELAY) is 200 ms.

Next Steps

At this point in the project, we have a reasonably functional plant light controller (and clock and temperature display).

Although using the new menu to set the date and time is definitely a lot more user friendly than the deposed serial port system (boo, hiss), it’s still a pain to have to re-enter each menu setting after a power outage. So, a battery backup and/or saving the menu settings to flash memory would be a nice feature, and I’ll definitely be adding that at some point.

However, the more exciting enhancement is to give the Netduino something to display on its LCD other than the time and temperature. There is a world of data swirling around the Netduino: twitterers tweeting, bloggers blogging, weather forecasters making stuff up forecasting. Wouldn’t it be cool if our Netduinos could dip into that datastream and display some of that info on its LCD?

There are 2 main options for implementing this: using a Netduino-compatible Ethernet shield to connect directly to the Internet, or using a wireless XBee device to connect to a PC. The first option has the very strong advantage of being a standalone solution, but I’ll be going down the second path – it has the even stronger advantage of me having already written most of the code.

So, if you’re interested in following me there, you’ll want to invest in a couple of XBees, and a couple of hardware interfaces for connecting the XBees to your Netduino and your PC.  More on that in my next article.

The source code for this article can be downloaded here: LightControllerWithMenu.zip. As before, it contains a reference to the Micro Liquid Crystal library, so you should download it as well, and place it in a folder that is side-by-side with my LightControllerWithMenu folder (e.g. c:projectsLightControllerWithMenu and c:projectsMicroLiquidCrystal).

This entry was posted in .Net, Electronics, Programming and tagged , , . Bookmark the permalink.

6 Responses to Netduino: Ordering Off the Menu

  1. Confused (my normal state of affairs) says:

    Can you point me to any examples of how to use the Seeed Studio Serial LCD v.9 (http://garden.seeedstudio.com/Twig_-_Serial_LCD_v0.9b) in this project? For that matter, is the Grove serial LCD (plugged into the Stem basic shield) even compatible with the Netduino?

    Thank you!

    • Dan says:

      Hi,

      Unfortunately (or maybe fortunately?), I don’t own a Serial LCD Twig, so I can’t try it myself. However, based on what’s in the Arduino library downloaded from their Wiki (http://garden.seeedstudio.com/index.php?title=Twig_-_Serial_LCD), it looks like they’ve used non-standard commands – not the same as Matrix Orbital or the Sparkfun Serial LCD.

      For example, the command for clearing the screen is 0x9F 0×65. Most of their commands start with 0x9F, rather than the 0xFE used by both Matrix Orbital and Sparkfun. (The commands are mostly the same for their more recent versions of the Serial LCD twig.)

      As a result, none of the LCD classes that I wrote will do anything with this LCD.

      However, unless they’ve implemented the hardware serial interface in a non-standard way (unlikely), you should be able to get it working with the Netduino by writing their command codes to the serial port. The SerialLCD.cpp and SerialLCD.h files in their SerialLCD library list all of the commands that they support.

  2. Somewhat less confused says:

    Thanks for taking the time to reply! I thought the same, so I tried re-writing your Matrix Orbital library using the codes from their SerialLCD.h header, Alas, all I have to show for my trouble is a line of black squares on the LCD. I’m assuming (always dangerous, I know) that the 4-pin Digital 1, Digital 2, etc. ports on the Stem correspond to the D1, D2 ports on the Netduino, so that I can access the LCD plugged into Digital 1 simply writing to COM2. Or am I even more confused than I’d feared?

    • Dan says:

      Hi,

      I hadn’t noticed this when I first replied, but there is an unusual initialization sequence for the serial LCD that is documented in the Wiki http://garden.seeedstudio.com/Twig_-_Serial_LCD_v0.9b.

      In their SerialLCD library, the code that handles this is in their begin() method, which has to be called before the LCD will start working.

      Basically, their begin() method loops until it receives a 0xA3 on the serial port, then it sends a 0xA5, then it loops until it receives a 0xAA. At that point, the LCD is ready for commands.

      So, unlike most Serial LCDs, this one talks back.

      As a quick test, you can have the clsLCD_MO constructor wait a couple of seconds after it opens the SerialPort, then send the 0xA5, then wait a couple more seconds before continuing. I’m just guessing at the timing, of course.

      If you want to play by their rules, clsLCD_MO doesn’t have any code that reads from the SerialPort, but clsLCD_SF does. However, clsLCD_SF uses interrupts rather than polling, which is generally preferable but in this particular case is more cumbersome than necessary.

      You can implement polling similar to the begin() method in their SerialLCD library using the SerialPort.BytesToRead property and the SerialPort.Read method. The latter will throw an Exception if there isn’t anything available to read.

      I notice that their new library for the newer hardware version of the Serial LCD twig handles things even more unusually: rather than polling for the 0xA3, it powers the LCD off and on (your version doesn’t support that), then sends the 0xA5. That might mean that the Serial LCD chip stops looking for the 0xA5 after awhile, and if the Netduino doesn’t respond in time it just plain won’t work.

      The other thing, which you’ve probably already checked, is to adjust the contrast potentiometer – if the contrast is turned up all the way, you’ll get the black rectangles. It appears to be a blue box on the end of the “twig” board.

      Please let me know how it goes. It seems that the current Serial LCD twig uses almost the same interface, so I’m sure I (and other Netduino users) will eventually be using it too.

      Dan.

  3. Somewhat less confused says:

    I missed the part about the initialization as well. Serves me right for skimming.

    I’ll cobble it together and give it a try (and let you know how I do).

    Thanks once more.

  4. Confused again says:

    I tried a quick-and-dirty but it still didn’t work. And adjusting the contrast didn’t help either. Sigh.

    I’m still not convinced the LCD is even communicating with my Netduino. I’ll poke around some more and let you know.

    Thanks again for your kindly assistance.