CHAPTER 7 ■ CONTROL HUBS 253 In the TODO example, your applet should be able to list the to-do list for the current user or the public and optionally be able to sort it alphabetically. 5 This will ultimately provide you with five potential parameters: • Refresh state parameter: Which user is visible? • Refresh state parameter: Is the list sorted? • Command argument: Switch to user X. • Command argument: Sort list. • Command argument: Unsort list (aka, show in order). This demonstrates the next point—there is redundancy here. It is technically possible to combine the refresh state and command parameter in one, since they both control the same data. However, when you are building a web page, you need to know all the refresh state parameters so that the other links on the page have the correct values. Unfortunately, that would require a lot of work to know which state parameter would be overridden later by the command parameters. You can simplify this by writing a refresh function that describes the current state and that every other applet will indirectly call when it requests a URL from the applet manager: function getRefreshParams(&$appMan) { return $appMan->getArgument($this, "user", $this->_viewuser)."&". $appMan->getArgument($this, "sort", $this->_sortlist)); } You next add links that contain command parameters, which are similar to those you’ve seen already: $html = "Show: "; $html.= $appMan->getAppletLink($this, "dosort", "0", "Chronologically")." "; $html.= $appMan->getAppletLink($this, "dosort", "1", "Alphabetically"); $html.= " For: "; $html.= $appMan->getAppletLink($this, "douser", $user, $user)." "; $html.= $appMan->getAppletLink($this, "douser", "public", "Public"); 5 To correctly delete an entry from the TODO list, you’d need to lock the file in case the file got corrupted when two people tried to delete at the same time. I have a truly marvelous solution to this, which this margin is too narrow to contain! CHAPTER 7 ■ CONTROL HUBS 254 These parameters, by convention, are prefixed with do indicating that they should change the refresh state. That is, new state = old state + do changes. The applet manager generates a suitable link by gathering the refresh parameters from every applet present on the current page and appending these do links to the end. When the page is loaded, a new state is built based on these parameters and done in two stages. The first is to retrieve the refresh arguments: $this->_sortlist = $appMan->queryParameter($this, "sort", false); $this->_viewuser = $appMan->queryParameter($this, "user", "public"); The second is to look for any do parameters to change this state: $this->_sortlist = $appMan->queryParameter($this, "dosort", $this->_sortlist); $this->_viewuser = $appMan->queryParameter($this, "douser", $this->_viewuser); In both cases you’re using a default argument to queryParameter that covers the case when the applet is first used and no parameters at all are available and for when there are no command parameters. You can then flex your creative muscles in displaying the output from the Bearskin command todo (remember writing that all those pages ago?!) and write the list into the HTML: exec("/usr/local/minerva/bin/todo list ".$this->_viewuser, $todolist); if ($this->_sortlist) { sort($todolist); } $html .= "<ul>"; foreach($todolist AS $item) { $html .= "<li>$item</li>"; } $html .= "</ul>"; To add a layer of polish to these, you could move the exec call into Zinc, but that can be left for another day! Global Configuration There are a small number of configuration files used in the web portion of Minerva to cope with the different directories structures you might adopt, as detailed in Table 7-1. CHAPTER 7 ■ CONTROL HUBS 255 Table 7-1. Web Configuration Directories Include Filename Function Default Directory Description minerva.conf getMinervaRoot /usr/local/minerva The base of the Minerva system itself. system/setup.conf getURLRoot /minerva The name used by Minerva web components. Can be changed for protection against bots that attempt to break any web pages held in a Minerva-named directory. system/setup.conf getPathRoot /var/www/sites/homecontrol The filesystem path of the web root. Used when you need to access files in the conventional manner. system/utils.inc getServerName V aries. Use this, instead of IP dotted quad if virtual servers are used. system/utils.inc getServerPort 80. system/utils.inc getRemotePort V aries b y client. system/utils.inc getIPAddress Determine by client. Might actually be IP of router. Applet Configuration There are two different types of directory you, as an applet writer, need to consider. The first are those that are used to serve web data to the client, such as images, configuration data, or supplementary files. There are several methods inside each applet class to retrieve this, each accepting a filename and returning its full path, such as getConfFileName (taken from the configuration directory), getAppletFileName (the applet code directory), and getImageURL (the images directory inside the applet folder). The second type of directory is one that refers to a location in the filesystem and is referenced with getFilesystemPathStub and concatenated with the relative filename. In reality, any relative web path can be converted into a filesystem path by joining it with WarpSetup::getPathRoot, but these methods provide a clean way of writing code. There is also an intriguing method called getRefreshTime, which causes the current web page (with all its applets) to automatically reload itself every n seconds. This allows the applet to more easily reflect changes to data without needing to implement specific push protocols. If more than one applet supports getRefreshTime, then the shortest time is used. This is provided as an alternative to the use of Ajax (as demonstrated in the Bluetooth, currency, and recipe applets) that asynchronously responds to requests CHAPTER 7 ■ CONTROL HUBS 256 from the main server. Remember that most browsers support only two concurrent Ajax requests, so their issue should be staggered with a timeout. Utilities Various utility methods are included as part of the applet manager, as well as the individual applet base class itself. Indeed, there are even full classes that can be derived from to create near-complete applets with very little work. Warp_Browser_Applet, as used by the MP3 player and video streamer, lets you traverse an entire directory structure without writing a single line of code; you only need to overload the renderFileLine and renderDirectoryLine methods to generate appropriate actionable links. Additionally, Warp_Static_Text_Applet can be used select and render one of many given HTML files, as demonstrated with the cooking applet. Caching is one of many utilities provided by the appletUtils class, located in warp/warplib/appletutils.inc. Code like this will download the contents of $url to the local data file but only if the file doesn’t exist or is older than 6,000 seconds: $contents = appletUtils::getContents($url, "local_data_file", 6000); The cache contents are stored in /var/log/minerva/cache. Release If you’re developing an applet for yourself, then the job is now done! Otherwise, you should package it ready for others. The addminervaapplet script is used to install new applets into the correct locations. Since there can be several components to an applet (Bearskin, WARP, and Zinc), you should create directories for each so that it matches those used already. Here’s an example of the FM radio applet: fmradio/example.php fmradio/Readme fmradio/version fmradio/fmradio/bearskin/fmradio fmradio/fmradio/install/install.sh fmradio/fmradio/warp/app/ [contents of applet directory go here] fmradio/fmradio/zinc/conf/ [Zinc configuration here] fmradio/fmradio/zinc/cmd Manifest The Manifest system is a method of presenting multiple elements in a sequential pattern in a way that can be interactively terminated, interrupted, or extended, with the commands stop, next, and more, respectively. This is better explained by working through the two supplied examples, News and Music, whose audio-based output is typical of the usage of Manifest. The news manifest reads headlines from a given news feed one at a time. If the more command is given at any point during the headline, the full story is then read, before continuing with the next headline. (In the case of the music manifest, the more command is a null operation but could be used to speak the title and artist of the previous track.) The manifests can be invoked with a simple command like the following: CHAPTER 7 ■ CONTROL HUBS 257 manifest default start music 10 and, since the current manifest is known, can be controlled without naming it: manifest default next Note that the start command is synchronous and doesn’t return until all the items have been played, which will be either when there is no news left or the maximum number of items have been read, in this case 10. Every manifest has the same set of driver commands, based in a suitably named directory under $MINBASE/etc/manifest. These commands are held in files: onstart: This is an optional script that triggers an introduction to the manifest as a whole. This could be an initial “here is the news” kind of announcement. The first element of the manifest should not be played here, however. onmore: This is another optional script, covering the additional information to be played. The script should exit with an error code of 1 to terminate the playback. onnext: This is obligatory and called once at the start to initiate the first piece of information and repeated for each element in the manifest. Like onmore, it should return an exit code of 1 to prevent any future results. onstop: This is called, optionally, at the end of the sequence and usually initiates a chime or conferment that the manifest has completed. This happens regardless of whether it ended naturally or by forcible termination. terminate: This kills any process spawned from an onnext output. It is optional and needed only for those scripts that launch additional programs, such as the media player that must invoke mp3player default stop. If this doesn’t exist, the process is killed using the standard Linux command. ■ Note You can connect the music manifest to Cosmic in order to trigger a few random songs at bedtime or read the news in the morning. The news manifest is programmed, by default, to read the top headlines from the BBC news site, while the music one will randomly search a given directory and play music it finds there. Marple Marple stands for the Minerva Appliance Routing and ProtocoL Engine. This is a mechanism whereby you can control a device, such as a TV card, from a command on one machine while using the command and resources of another. This allows you to spread the hardware load between machines or to distribute commands to remote servers that service peripherals that are ineffective in other locations— X10 gateways, notably. . $this->_sortlist = $appMan->queryParameter($this, "dosort", $this->_sortlist); $this->_viewuser = $appMan->queryParameter($this, "douser", $this->_viewuser);. to retrieve the refresh arguments: $this->_sortlist = $appMan->queryParameter($this, "sort", false); $this->_viewuser = $appMan->queryParameter($this, "user",. getRefreshParams(&$appMan) { return $appMan->getArgument($this, "user", $this->_viewuser)."&". $appMan->getArgument($this, "sort", $this->_sortlist)); } You