In episode 5 of the Multifunction multimedia machine series , you add the following two important pieces of functionality to your multimedia appliance and examine the implications from the point of view of a commercially fielded appliance:
- The ability to remote-control the device using any convenient Web browser.
- A local user interface provided using the ELinks or Links2 embedded Web browser.
I have a lot of material to cover in this episode, so I am not going to use text space going line by line through the source code. Please refer to the comments in the code for more details on how I achieve the features I describe below (see the Download section for the code itself).
The appliance you have assembled to date, assuming of course that you've been following along with these articles, already has most of the infrastructure for item 1: you already have a functional Web server (thttpd) and a main application (ibmslides), so all you really need is a control interface on the application part and a way of connecting the Web server to this control interface.
A simple way of achieving this is to build a CGI back-end program, called by
the Web server, that communicates data to the main program through a named
pipe. As a minor administrative detail, I've chosen to implement this CGI
program inside the ibmslides program itself. If you examine the Makefile in
the downloaded source tarball, you will see that "make install" now installs a copy of the ibmslides program at /web/cgi-bin/admin.cgi. The main.c
argv to see how the program was invoked; if the substring
"cgi" is found in that path, the program assumes its CGI persona, grabs the
input, and talks to the named pipe /tmp/slidepipe.
Note that the above hack works on the assumption that you are using the /etc/thttpd.conf configuration file I left you with last episode. If you modified that file so CGI scripts don't live in */cgi-bin/*, then make sure you call the alias something.cgi to make sure it trips the dual-personality logic correctly.
I won't go into a whole bunch of detail on the mechanics of how to acquire and process CGI data submitted in a Web form because I covered the introductory details a while ago in "Migrating from x86 to PowerPC, Part 4" (see Resources). This piece of code uses the same technology; it just applies it in a slightly different way. The interesting part is that instead of simply generating a Web page directly, the CGI back-end program uses that named pipe to talk to the main application and transfer state and control information through that mousehole.
If you look at cgi.c (again, linked in the Download section), you'll see all the code in its full glory. The "main program" side of ibmslides forks a second (child) process that sits around listening to the named pipe. When something of interest comes in, that child process messes around inside the address space of the main program to carry out whatever task needs to be accomplished. The "CGI grabber" side of ibmslides runs only when you click a link on the Web page; it analyzes what you did on the Web page and generates the appropriate strings to send to the named pipe to converse with the other half of its brain.
To test it out, simply extract the source tarball, enter the directory thus created, then:
Listing 1. Installing the sample code
# make # make install # cp index.html /web
Now point your browser at http://a.b.c.d/ (where a.b.c.d is the IP address of your multimedia machine) and you can play with some simple remote control functionality on the mad multimedia appliance.
An extra tip for people with degrees in advanced cleverness: The CGI program I've written for you detaches from the named pipe every time it exits. This means that it is possible for another program or programs to write to the same pipe and thereby access whatever control interfaces you expose through this system. If you have an off-the-wall or simply custom piece of input hardware, you can exploit this design feature by writing a daemon that reads your hardware and posts appropriate messages to the named pipe. You can even have the system enumerate what's attached to it at power up, and decide which "shim" daemon to run accordingly.
The most obvious example of where this might be useful is if you want to add an infrared remote to your widget. Infrared remote protocol decoding and encoding functionality is provided in Linux® through LIRC (see Resources). LIRC consists of several modules. At the lowest level are some installable kernel modules that access various sorts of infrared hardware, and above that layer is lircd, the daemon that decodes incoming IR protocol data. Many people will run lircmd on top of lircd; lircmd takes the decoded outputs from lircd and transmutes them into user interface messages such as mouse movement or keyboard press and release events. However, it's entirely possible to write your own program that listens directly to lircd output and does something intelligent with it.
By the way, the original Mac mini doesn't include infrared support, so perhaps a Bluetooth remote might be more feasible on this platform, but the same general principle applies.
Some downright fox-like readers will doubtless envision the possibility of controlling farms of multimedia devices over that pipe interface using NFS mount points. Unfortunately, I have to burst your bubble on that one -- named pipes don't work between machines across NFS -- ten points for thinking, though. If you want to achieve the kind of functionality I just described, instead of using a named pipe you should use a socket. However, you'd also need to write a custom application on the remote computer side to talk to this socket; using a general-purpose Web interface is preferable, at least for the home-user scenario.
At this point, a slight diversion into commercial practicality is in order, because I'd like to bring to your attention the fact that simply Web-enabling an appliance does not automatically make it end-user friendly. The modern mantra says that today's consumer understands the Internet and hyperlinks. Therefore, adding a Web server to your product wraps its user interface in a familiar skin, thereby inherently making your product user-friendly. There's a large kernel of truth in this, but (if I may continue the nut analogy) it's a macadamia kernel. For those of you who don't know what this implies, read the appropriate link in Resources; the salient point is that a macadamia shell takes 300lb per square inch of pressure before it cracks.
In the good old days when I used to work on commercially produced appliances like the one described here, the single largest waster of telephone support hours was customers having difficulty connecting to the device. I warn you from personal experience that it is excruciating to talk customers through operating their routers and other networking equipment; you're usually looking at a very large and complex problem through a very small keyhole.
If you intend to field a device that contains TCP/IP networking features, I strongly suggest you think about how to implement a fallback mechanism and methods for the customer to test that all the various parts of the network are operating correctly.
Ensuring usability is a multifaceted task, but three underlying issues are:
- Connectivity (an ability for A to talk to B)
- Discovery (A determining automatically that B exists, and where it is), and
- Service enumeration (A determining automatically what B is capable of doing, and what protocol to use for issuing commands)
I'll start with the connectivity issue. In these articles, I've been telling you to go do things to IP address a.b.c.d (not specified, since it will vary according to your network setup) and assumed that you know how to find out your multimedia machine's IP address, perhaps by looking at the DHCP lease summary page of your router. I can get away with this by relying on the fact that either you are all developers and technical persons who know what to do, or you're reading for leisure and won't actually try this so it won't matter if you can't get it working.
Most home and small-business users are going to plug your device into a small consumer-grade router designed to share a broadband Internet connection. This router will have a DHCP server in it to issue IP addresses on your local subnet. Unfortunately, all sorts of problems can crop up with these devices, especially if you're trying to attach an embedded system. It seems that manufacturers of consumer computer peripherals take a perverse delight in seeing just how far they can deviate from RFCs and still have a Windows® or Mac system recognize their appliance without complaints.
How do you handle the situation where your device can't acquire an IP address from a DHCP server? This occurrence might mean either that there is a problem with the server, or it's incompatible in some bizarre way, or it might simply mean that there is no DHCP server at all. Generally what you will do is have a manually configured fallback IP address, netmask, and gateway; if you don't get anything from a DHCP server, use the fallback parameters. Ideally these parameters should be user-configurable.
For remote troubleshooting purposes, you should supply an Ethernet crossover cable with your device. If you have any question about functionality, you can establish whether the device is working very simply: just tell the user to unplug his or her computer and the appliance in question from the network, connect them directly to one another using the crossover cable, and restart both machines.
What IP address range should you use under such circumstances? Modern operating systems by default implement APIPA (see Resources) so that they can provide at least rudimentary network functionality in an unmanaged network environment with no DHCP server. In brief, APIPA specifies that if an IP address cannot be acquired, the device will assign itself a random address in the range 169.254.0.1 through 169.254.255.254. The device is supposed to use ARP to verify that the random address it chose is not already spoken for. In practice, I have seen devices that choose IP addresses based on other factors, say, the lowest 16 bits of the MAC address. These devices simply assume that they are not going to collide with anyone else, which is a fairly safe assumption if you're only using this feature as a special one-to-one test mode with only one PC and one appliance on the network. Otherwise, sometimes it's not so safe...
By whatever means, you now have your hypothetical consumer appliance connected to a network in such a way that it can be contacted by a computer on the same network. However, this brings you to the next two problems, namely, discovery and service enumeration. The holy grail of discovery protocols is to be able to plug a new appliance into the network, have an icon for it appear on the user's desktop, and arrange things so that double-clicking the icon brings up the embedded Web interface of the appliance, or some other interface appropriate to the particular device.
There are several attempts to standardize on methods for discovering and interacting with appliances on disparate sorts of networks (see Resources). However, at this time I'd characterize them all as basically unsupported in the real world. (Yes, some appliances support these protocols, but the market is very fragmented. It is fair to say that none of these standards has yet achieved critical mass.)
The Universal Plug'n'Play (UPnP) protocol was supposed to be the be-all and end-all of discovery, but in my opinion the standard overreaches and fails to preserve either simplicity or ease of interoperability with other systems. (I wrote a scathing article about these issues five years ago; see Resources. From studying the UPnP home page today, I can see that almost none of my original annoyances have been dealt with in the interim.) Apple's Bonjour protocol is another system that enjoys some market penetration, though its use seems to be confined mainly to networked printers. There are several others, as well -- take your pick, according to what industry you work in. UPnP is the only such protocol supported natively by Windows, but most users run with it disabled for security reasons (see Resources for more information).
Besides general-purpose discovery protocols, you can also roll your own proprietary system. (The software shipped with the Kuro Box, which I use in the aforementioned Migrating x86 to PowerPC series, works this way.) In brief: you write a proprietary application for whichever host operating systems you care about, and that sends a magic broadcast message to the entire network. When one of your appliances hears the magic broadcast, it holds off for a random time (to reduce collisions), then sends a "hi, I'm here" packet back to the computer that originated the broadcast. The computer picks up that response packet and then uses proprietary communications methods to connect to the appliance.
Some routers let themselves be "discovered" by the interesting technique of emulating a DNS -- if you go to your browser and enter "router," the router itself intercepts that DNS lookup (rather than passing it upstream) and returns its own IP address. This option isn't really available to appliances like the one in this series, but it's an interesting idea and might be useful to you.
So much for remote control, for a while, anyway, and on to local user interfaces. Way, way back in the introduction to this series, I waxed quite lyrical on the importance of choosing the right user interface for the right job. I also mentioned that a nifty, but rarely used method of achieving the same UI on the appliance as on the remote connection is simply to build an embedded browser into the appliance itself. Since you now have a functional HTTP-based user interface, you can exploit that by installing a local Web browser on your box.
Obviously you need some kind of input device to invoke and interact with this browser. Since this series works with off-the-shelf hardware, I don't want to get into too many of the exotic options you might have for physical interface methods. In particular, I want to stick with code that you can actually run on hardware you're likely to have lying around, without requiring you to purchase special-purpose peripherals.
Hence, for the purposes of this discussion, I assume that you're using a touchscreen monitor, and you've configured X to work with it as a pointing device. The crafty part of this decision is that if you don't in fact own such hardware, you can simulate it with a mouse. (Note, by the way, that this is why I designed the interface page to use only clickable items, without any text entry fields.)
You might consider a plethora of Web browsers, but you probably don't want to leap for a full-featured browser like Firefox. A big browser like that takes a relatively long time to launch, which is annoying -- if the user presses a button on a physical piece of hardware, a response is typically expected more or less immediately. One workaround for this is to keep the browser loaded at all times and merely bring it to the foreground when you need it -- but this has a big downside in terms of the RAM or paging file hit (or both) required to keep such a large program permanently resident.
The two Web browsers I've linked in Resources are ELinks and Links2. Both of these are derivatives of the faithful lynx; they're both tiny and elegant, and they are both a good choice for embedded use. They have slightly different applications, however -- ELinks is a simple text browser, good for very constrained systems or systems that lack a graphical display. I'd recommend ELinks (adapted) if your system has multiple display elements and you need to run the browser on, say, a smaller monochrome or text-only display. For a large graphical display like the one we're using, Links2 is more attractive (and more closely approximates what the user will see remotely on a regular desktop browser).
You don't actually need to build ELinks, since it's included in the Linux distribution you installed back in the first article. However, if you wish to build a newer version, just unpack the tarball and run:
Listing 2. Installing ELinks
# ./configure # make # make install
To build Links2, simply unpack the source tarball and run:
Listing 3. Installing Links2
# ./configure --enable-graphics # make # make install
The configure script will automatically detect framebuffer, SDL, and X support on the system as you have built it so far.
I've modified ibmslides so that when you click the mouse button (the left button, if your mouse has more than one), the program tries to run /usr/local/bin/links -- if that's not found, then it attempts to run /usr/bin/elinks. The slide show itself is paused while the browser runs, otherwise the graphics rendering code would overwrite the screen contents and obliterate the browser window. Normal operation resumes after you quit the browser.
Note that the exact command line used to run Links2 is:
Listing 4. Invoking Links2
This forces the browser to run in graphical mode.
That's it for this episode. In the next, and possibly last, installment of this series, I'll talk about adding security to embedded appliances for this application specifically and also some more general commentary. In a world where every sample project has an offhanded comment that "a real system should be more secure," it'll make an interesting change.
Contact the author for the downloads.
If you haven't dealt with APIPA before, you might appreciate this
good (simplistic) reference on APIPA.
gives some more detail and context.
For more information on how I process CGI input in a minimalist way, see
"Migrating from x86 to PowerPC, Part 4" (developerWorks, April 2005).
Five years ago, I wrote an article lambasting the over-engineered discovery
protocols then on offer.
The UPnP home page describes
-- in excruciating detail -- how to implement the various facets of
"standard" UPnP devices.
UPnP hasn't always been as secure as we might have hoped; this article
about serious security holes in early UPnP implementations.
The security holes are more
complicated than some people reported -- of particular note is that one
commonly recommended fix didn't work!
Apple's Bonjour has similar goals to UPnP but gets there by a different
Another discovery protocol is DDDP from AMX; read this press release
about DDDP for
some of the names involved in that standard.
If you want to attach an IR remote control to a Linux widget, the easiest
way to do it is by using LIRC.
You might appreciate a good
description of the difficulty of cracking macadamia nuts.
Get products and technologies
Download the source code referenced in this article from the table above.
warning applies to Internet Explorer users - make sure to save the file as
Download the latest version of ELinks from the
ELinks home page.
The specific version I used was elinks-0.11-20060703.
Download the latest version of Links2 from
the Links2 home page. In this text, I am using links-2.1pre22. The download page includes a lot of dire warnings about dependencies and such -- don't worry, all the
libraries you need are already included in the Linux distribution we're using.
Lewin A.R.W. Edwards works for a Fortune 50 company as a wireless security/fire safety device design engineer. Prior to that, he spent five years developing x86, ARM and PA-RISC-based networked multimedia appliances at Digi-Frame Inc. He has extensive experience in encryption and security software and is the author of two books on embedded systems development.