NAME
    mh - MisterHouse Home Automation Program

SYNOPSIS
    mh is a perl program for time, event, web, socket, and voice based home
    control functions. It currently runs on Windows 95/98/NT and on Unix
    platforms, including Linux and Mac OSX.

    perl objects are used for various types of objects to give a powerful
    but concise programming interface. Here is some example code:

       $dishwasher = new X10_Item 'B1';         # B1 is the dishwasher X10 code
       set $dishwasher ON if time_now '10:30 PM';

       speak "I am now closing the curtains at $Time_Now" if time_now "$Time_Sunset + 0:15";
       speak "The time is $Time_Now" if time_cron '30,35,40,45,50 6 * * 1-5';

       play(file => 'stairs_creek*.wav') if state_now $movement_sensor eq ON;

       $v_bedroom_curtain = new Voice_Cmd '[open,close] the bedroom curtains';
       curtain_on('bedroom', $state) if $state = said $v_bedroom_curtain;

  Usage Options
    mh [options] [files]

    options are controlled in the mh\bin\mh.ini file. You can override the
    default mh.ini parameters with 'private' parm files and point to them
    with a mh_parms environmental variable.

    files is a list of members in the -code_dir that you want to run. The
    default is to run all .pl files.

  Usage Examples
    These give the help text:

       mh -h
       mh -help

    This is the normal usage:

       mh

    This will only run the 3 specified members:

       mh items.pl test1.pl test2.pl

    This will disable the tk interface and change the code dir to the Bruce
    directory:

       mh -tk 0 -code_dir c:\mh\code\Bruce

    This will log all 'print' statements to test1.log and turn on all serial
    related debug messages:

       mh -log_file test1.log -debug serial

  Installation
DESCRIPTION
  mh overview
    Items and events are defined by using your favorite text editor and
    creating members of perl code in a directory of your choice. For
    example, all the code that runs my house is in the /mh/code/bruce
    directory.

    After starting mh, in addition to the timed, serial, or X10 based events
    that you defined, you can trigger mh actions with any of the following:

      - Voice commands.  On Windows platforms, you can use the MSVOICE icon in your tray
        (the green V) to control the Voice Recognition (VR) mode (manual,
        keyword-activated, or continuous). You also select your TTS (Text To Speech)
        voice with MSVOICE.

        On Unix platforms you can use the Festival TTS engine and pick among different
        accents and languages.  On Linux, you can install IBM's ViaVoice SDK
        to enable Voice Recognition.

      - A web browser pointed to http://localhost:8080 .  In addition to several
        example web interfaces, if you are familiar with HTML, you can write your
        own customized interface.

      - Typing any voice command in a telnet window.  This is customizable
        with the telnet.pl module.

      - Typing any voice command in the command box in the mh tk gui window.

      - Typing any voice command from a dos box, using the mh/bin/house command

      - Having any program write voice commands to the 'xcmd_file' (see mh.ini)

      - Triggering on user defined Socket or Serial port events.

    Here are the basic steps of the mh program:

       1: Read config files and initialize things

           Setup the OLE Text To Speech (TTS) object or Festival socket port
           Setup the OLE Voice Recognition object or socket to the viavoice_server on linux
           Setup the serial objects
           Setup the tcp socket ports

       2: Read and evaluate/compile the user event code into the mh_temp.user_code file:

           Optional *.mht files are processed by mh/lib/read_table_*.pl, creating *.mhp files
           Objects are evaluated and created.
           Everything else is put into a &user_loop function (at the end of mh_temp.user_code file).

       3: Loop until exit, each pass executing:

           Set global time/date variables
           Check for voice commands
           Read/write socket data
           Read/write serial data
           Check for timers actions
           Check for external command files
           Evaluate the &user_loop function
           Sleep for a while (default is 100 milliseconds), so mh does not hog the CPU.

    The interpreted nature of perl allows us to change/add/delete an event
    member quickly, without re-starting mh. After changing code, you tell mh
    to re-load the user event code (step 2 above). If there is a syntax
    error with the new code, the previous version of the code is re-loaded.

    Re-loading 20 event members with 1500 lines of event code takes about 2
    seconds. With a 50 millisecond sleep time, mh takes about 2->5% of the
    CPU and loops about 18 times per second on our 150 MHz PPro system.

  Perl Info
    There is lots of good perl info is at http://www.perl.com . The
    reference book is "Programming Perl, 3rd edition" by Larry Wall, Tom
    Christensen, & Randall Schwartz. ISBN 1-56592-149-6. It is available
    from http://www.oreilly.com . "The Perl Cookbook" is another good book
    that has a ton of examples in it.

    I fun way to slowly learn perl is with to get a perl FAQ per day. You
    can sign up here: http://perl.faq-by-day.org

    For those of you using the compiled, instead of the interpreted, version
    of mh, the standard perl documentation can be found at
    http://www.perl.com/pub/v/documentation ,
    http://language.perl.com/CPAN/doc/manual/html/index.html and/or
    http://www.activestate.com/ActivePerl/docs

    Perl can be a pretty intimidating language to learn, due to its many
    options and functions, but for the purposes of using it to code mh
    events, there are only a few simple rules to keep in mind. Here are a
    few of them:

       Variables start with a $

       Strings are quoted with ' or ". Use " if you want variables within
       the string to be substituted.

       Commands end with ;

       Comments start with #

       You can implement block comments with =begin, =cut records, as follows:

          print "this will run\n";

          =begin
          print "this";
          print " will not run\n";
          =cut

          print "all done\n";

       Do loops start and end with { }

       If statements can be done either of the following ways:

          if (test) {action}
          action if test;

       If tests are == (or !=) for numeric data and eq (or ne) for string data.

       Object methods can be specified 2 ways.
       Here are both examples on how use the method 'set' for an X10 object:

          set $dishwasher ON;    # Indirect object form
          $dishwasher->set(ON);  # Classic 'object oriented' form

       Regular expressions are used to parse strings.  They are very powerful
       and can be quite complex, but here are a few simple rules:

          They are triggered with the =~ operator
          By default, they are delimited with //
          .  stands for any character (including blanks)
          \S stands for non blank characters (\s is for blanks)
          \d stands for digits
          * is a modifier that means 'zero or more of the previous type of character'
          + is a modifier that means 'one  or more of the previous type of character'
          ? is a modifier that means 'zero or one  of the previous type of character'
          () captures whatever is inside for use via $1, $2, $3, etc

       For example:

        $data = 'Mar 25, 1998 - 11:53 AM EST / 1998.03.25 1653 UTC';
        $date = $1, $year = $2, $time = $3 if $data =~ /(.+), (\d\d\d\d) - (.+) EST /;

       On Windows systems, path names can use the Unix convention of / instead
       of the DOS convention of \.

  List of supported hardware interfaces
    For a list of the hardware in Bruce's house, see mh/pod/faq.* question
    6.8.

    You can get the ActiveHome kit for $50 from at http://www.x10.com . This
    includes a 2 way X10 CM11 interface a lamp module and a couple of remote
    controllers. They also sell the CM17 firecracker interface which mh
    supports, but is a one way interface only (does not receive X10
    signals).

    X10.com also sells a $50 IR transmitter that works through the CM17
    interface. Info is at http://www.x10.com/products/ux17a_bj2.htm. This is
    supported via the mh IR_Item object.

    Another nice X10 option are the X10 RF -> Serial interfaces. The MR26A
    is $30 from http://www.x10.com/products/x10_mr26a.htm . The W800RF32 is
    $50 from http://www.wgldesigns.com/w800.html . A comparison of them is
    here http://www.wgldesigns.com/comments.htm . These will receive the
    information coming from most of the X10 RF devices (e.g. motion sensors,
    keychains, palmpads, remote controls like UR19, UR47, JR20). The
    advantage to receiving X10 data this way, instead of a powerline device
    like a CM11, is it is much faster as data goes from RF -> MR26A/W800RF32
    -> serial_port -> mh. Actions can be triggered in milliseconds, without
    having to wait for the 500+ millisecond time it takes to transmit data
    over the powerline. More info in mh/code/common/x10_rf_relay.pl .

    This new RF->X10 interface now supports all housecodes (older ones only
    support once housecode each):
    http://www.letsautomate.com/10680.cfm?CFID=67097&CFTOKEN=4528584

    Another supported X10 interface is the Marrick Lynx10-PLC controller:
    http://www.marrickltd.com See mh/lib/Lynx10PLC.pm

    A nice summary of the different X10 controllers is here:
    http://www.geocities.com/ido_bartana/Which_controller.html

    A survey of common X-10 devices and some hints as to their behavior is
    here: http://www.shed.com/x10stuff.html

    Here is yet another site with X10 info: http://www.x10ideas.com

    Europeans can get X10 stuff from http://www.intellihome.be ,
    http://www.hwg-telekom.de , www.madomotique.com, www.letsautomate.com,
    and www.laser.com .

    Australians can get gear from www.digihouse.com.au , www.eon3.com ,
    www.dse.com.au , and www.tecsol.com.au .

    Note that the CM12 is the equivalent of a CM11 for 220v/240v locations.

    Many X10 noise issues can be solved with the LEVHCA02 repeater and
    bridge, available here for $65:
    http://www.worthdist.com/Leviton/hca02-10e.htm

    Weeder Technologies, http://www.weedtech.com , offers the following kits
    priced from $30 to $50 each. All of these kits can share the same serial
    port:

       2 way X10 interface
       12 bit Digital IO (can be input or output, switch or button)
       8 port Analog IO, 10 bit resolution
       Caller ID, name and number
       Outgoing DTMF phone monitor

    You can use 1->2 of the Weeder IO kits to drive the the 'universal relay
    kit' from http://jameco.com (do a search for 'relay card' or 128910).
    The $60 gets you 8 relays. Here is some more info:

    1. The board is sold as 8 relays complete, upgradable to 16. If you only
    want to use 8 relays(in a limited fashion), the kit is all you need.

    2. If you want more than 8, you need more than just the extra 8 relays
    them selves. - you will need 8 extra diodes(1n4148 in my kit) - you will
    need about 30 extra terminating blocks

    3. The board comes with spots for LEDs to show activity, you will need
    to provide those as well as the resistors to drive them(none are
    provided).

    4. As delivered, the Weeder board will not drive the relays, you will
    need to get 2 ULN2803 darligton transistor array chips to do that(The
    relays need 12V, the Weeders give 5V).

    5. The board has the ability to be manually driven, you will need to buy
    2 extra 8 pin dip switches to do that.

    6. It comes as a bare open board, so think about an enclosure of some
    type.

    7. All of these parts are available from jameco for nominal extra cost.

    Danal Estes reports another relay option is the ELK-924-4 "Sensitive
    Relay": http://www.elkproducts.com/products/elk-924.htm. He used a
    couple of the four packs. Work like a charm, have been in an attic for
    over a year, no problems. Smarthome carries these for $38 a four-pack,
    you can probably find them cheaper, lots of alarm system places carry
    them. Search on "ELK-924".

    Nico wrote an interface to the Velleman k8000 board:
    http://www.velleman.be/Product.asp?lan=1&id=9383, available from his
    site at http://yow.be

    The HomeBase.pm module to supports the JDS interfaces,
    http://www.jdstechnologies.com . See member
    mh/code/public/test_homebase.pl for example code.

    The Homevison.pm module to supports HomeVision controller,
    http://www.csi3.com/homevis2.htm . This controller does 2 way X10, 2 way
    IR, 24 digital IO, and can output its menus to TV. See member
    mh/code/public/test_homevision.pl for example code.

    The DSC_Alarm.pm module supports the DSC PC5400 serial printer
    interface. This module prints all events that DSC logs to the event
    buffer. These events include arms/disarms/alarms, user code(s) that
    performed action(s), installer access, etc. DSC does not log individual
    zone open/close nor 'entry chime' events. See the mh/lib/DSC_Alarm.pm
    for further information. See also the DSC_Alarm item below.

    The CPU-XA, Ocelot, and Leopard controllers from Applied Digital Inc
    (http://www.appdig.com/adicon.html ) are supported through network calls
    to the cpuxad daemon, part of the XALIB package, available at
    http://mywebpages.comcast.net/ncherry/common/cpuxad

    There is a $30 (assembled) pic base IR receiver you can order from the
    Netherlands ($3 shipping):

      http://www.evation.com/irman/

    In addition to the mh/code/public/irman.pl code, there is free winamp,
    windows, and linux software for it. The schematics that it is built from
    are also posted on the net (elsewhere), but for $30, it hardly seems
    worth building it from scratch.

    There is an X10 product called IR Commander that works with the
    Firecracker and sends infrared. It's not a learning remote and most
    people who try it are disapointed. The RedRat2 is supported by
    misterhouse, but doesn't seem too popular. David Norwood wrote the
    module for the UIRT2 and it is being used by a few on both Windows and
    Linux. He now also has a module for the USB-UIRT. The Ocelot is another
    option.

      FireCracker: http://www.x10.com/products/ux17a_bj2.htm

      RedRat: http://www.redrat.co.uk/

      UIRT: http://www.fukushima.us/UIRT2/

      UIRT USB: http://home.earthlink.net/~jrhees/USBUIRT/index.htm

    As of 6/2004, the send/receive USB RedRat3 ($99 from
    http://www.redrat.co.uk/ ) is supported using the windows xAP RedRat
    Connector:
    http://www.xapframework.net/modules.php?name=Downloads&d_op=viewdownload
    &cid=45. RedRat3 is a great product with a growing online list of
    downloadable IR codes.

    Here are 2 LCD displays with programmable serial interfaces:

      http://www.crystalfontz.com/products/634/index.html  $60

      http://linuxcentral.com  $80 ($101 with keypad interface)

    The first display is a bit newer, bigger, brighter, cheaper. But the
    second one has a version that include a 4x4 keypad interface (keypad
    available from DigiKey for $20). Both are 4 lines x 20 characters (they
    also have a 4x40, but no keypad interface). I ordered the 2nd one from
    linuxcentral, with a keypad from http://www.digikey.com (pn CKN6011-ND
    or GH5010-ND). A comparison of the 2, plus some nifty linux software for
    them, can be found at http://lcdproc.omnipotent.net

    An example on how to use these displays is in mh/code/bruce/lcdproc.pl.
    See the header of that file for more info.

    Here are a few other devices which look useful, but have not had code
    written for them:

     http://www.nirvis.com/  $180 2-way IR device

     http://www.cc-concepts.com/  iButton DigiLock interface as electronic keychain

    Nice big Alpha LED serial connnected signs are supported as of 6/2004. I
    picked up the Alpha 213C (a.k.a. BetaBrite, 2" x 24", 14 character) from
    the local Sam's Club for $150. Their web site only lists a bigger
    version, but info on BetaBrite I have can be seen here:
    http://www.betabrite.com/Pages/betabrite.htm . It displays in multiple
    colors, in either scrolling or fixed text. See Display_Alpha.pm header
    for details.

    The Serial_Item object can read and write to any serially connect
    device. There is example code in the code/public directory that shows
    how to interface to a ham radio TNC receiver to decode and track a GPS
    transmitters position.

    An better, object-oriented way to write code to interface to a new
    hardware device is in mh/lib/example_interface.pm.

    Byterunner offers lots of types of serial cards:
    http://www.byterunner.com. Note for windows users: with the PCI-800H, I
    found I had to click the 'use default baud rate' box under control panel
    -> device manager (under the 4th tab, data rate).

    Good sources of information on the WX200/WM918 weather stations are at
    http://wx200.planetfall.com/ and
    http://www.weatherwatchers.org/wxstation/WX-200/ . $200->$300 at Radio
    Shack or $200 from http://www.heartlandamerica.com (search for weather,
    model WW22- 3350). See mh/lib/Weather_wx200* for info on interface code.

    There is also now a new WMR-918/968/Radio Shack Accuweather, which is a
    wireless version, available for about $350. See
    mh/code/public/weather_monitor_wmr968.pl.

    See weather_monitor_ultimeter2000.pl for an example of interfacing to
    the Peet Bros. Ultimeter200 weather station: http://www.peetbros.com

    iButton is a family of devices that can all talk to a '1 wire'
    interface. Here are some URLs and prices:

      http://www.ibutton.com (pick shop online)
        - $15 DS9097U-009 or -S09 + DS1402D-DR8l for the serial interface
          Note:  The DS9097, DS9097E, or parallel port adapters will not work with mh
        -  $3 DS1990A-F5  for 64 bit ID buttons.

      http://www.ibuttonlink.com
        - The $35 LINK interface has been reported as more reliable and
          is more flexable than the DS9097 listed above.

      http://www.pointsix.com
        - They also sell the $15 interface (under iButton Products)
        - $10 for a 1 port relay card
        - $10 for a 2 port digital input or output card
        - $6  for a 1820 temperature sensor
        - Info on how to wire up a 1 wire bus:
          http://www.pointsix.com/cgi-bin/PointSix.cgi?AppNotes&app010

      http://www.aag.com.mx
        - Sells an iButton weather station for $80

      http://ibuttonlink.com/
        - An improved $35 serial interface

      http://weather.henriksens.net  has free 1-Wire Weather station software
        - It includes a tcp/ip server that (in theory) could talk to mh

      Ray Dzek has a nice how to get started tutorial here:
        http://www.solarbugs.com/home/ibutton.htm

      Neil Wrightson also has some 1wire info here:
        http://www.users.bigpond.com/nwrightson/1Wire%20Hardware.htm

      A Linux "One Wire File System" that sets up the one wire bus to look like
      a directory (not integrated to mh yet)
        http://owfs.sourceforge.net/

    If you want more reliable VR without having to use a headset, you can
    try these to digital array microphones:

     - Labtec LVA-7280 ClearVoice Digital Microphone ($130)
       http://www.labtec.com/product/family.cfm?CategoryID=2

     - Andrea Desktop array DA-400 ($150)
       http://www.andreaelectronics.com

    The Labtec has a push button on top to turn it on and off and a
    red/green LED that blinks green when sound is heard. The Andrea has a
    slide switch on the side to turn it on and off. The Andrea has a bit
    clearer sound and is about 2/3 the size. Having a hardware switch to
    disable VR is handy.

    Both of these mikes are meant to be used when you are at the desktop.
    The idea is that they filter out background noise better, so you can use
    them in slightly noise environments, where headsets were normally
    required.

    The mh/code/common/barcode* members code will process data from the Cue
    Kat keyboard simulator barcode scanner, which used to be free from
    places like Radio Shack but are now discontinued.

    If you would like to do call waiting caller id (normal callerid and
    caller id even while you are on the phone), you can a good deal on the
    NetCallerID box for $15 from here:
    http://www.dfwmetrotechs.com/misterhouse/calleridorder.html

  List of startup options
    The complete list of valid startup options is in the mh.ini file. Here
    are a few of the more important ones and their defaults:

    code_dir=\mh\code\test
        Points to the directory that has the user code files in it. This can
        be a comma delimited set of paths if you want to keep your code
        files between different directories.

    html_root=$Pgm_Root/web
    html_file=mh_default/index.html
        Points to where the web interface html files are.

    http_port=8080
        Use 80 if you want to use the normal web port (i.e. http://localhost
        ). If using something other than 80 (because you already have a web
        server running), add :port to URL (e.g. http://localhost:8080 ).

    server_telnet_port=23
        Use 23 to enable access via 'telnet localhost', if you are running
        the mh/code/common/telnet.pl member. If you use a different port,
        you can still get to the port with 'telnet localhost port_number'.

    diagnostics=1
        Set this (or w=1) to enable perl -w warning messages on potential
        code errors. mh runs about 10% slower with this option on.

    run=perl_script
        This parms allow the compile mh.exe to act as a general purpose perl
        interpreter, so we can run any arbitrary perl script, without having
        to have perl installed. See the get_weather.bat, get_tv_grid.bat, or
        set_clock.bat files for examples. This also allows is to use the mh
        run command to spawn a separate mh process to run longer running
        perl steps, even if we do not have perl installed. A Win32 'fork'
        command would be cleaner, but alas, no forks.

    debug=
    log=
        You can use the -debug and -log and options to turn on debug and/or
        log the console errata to a file. For example:

          mh -debug http -log http_socket.log

        will turn on socket related debug messages and log all errata to the
        file http_socket.log. You can also stack debug flags, using ; as a
        separator. fro example:

          mh -debug serial;x10

    time_start=
    time_stop=
    time_increment=
        Use time_start and time_stop to run mh in 'fast test mode', useful
        for debugging events. The time_increment parm sets how many
        simulated seconds to increment per pass. Default is 60. If only
        time_start is specified, normal mode will resume when the current
        time is reached. If time_stop is specified, mh will exit when
        reached. These parameters can be any time/date string, as recognized
        by the time_now function (see docs)

        Here are some examples:

          mh -time_start 0 -time_stop 24 -tk 0 -voice_text 0
          mh -time_start "6 AM" -time_stop "11 PM"  -time_increment 1
          mh -time_start "5/14 7:10" -time_stop "5/15 10 PM" -time_increment 300

  List of global variables
    Time/Date Variables

      $Time_Startup, $Time_Startup_time, $Time_Now, $Date_Now, $Date_Now_Speakable, $Year_Month_Now
      $Time_Sunrise, $Time_Sunset, $Time_Sunrise_Twilight, $Time_Sunset_Twilight
      $Second, $Minute, $Hour, $Mday, $Wday, $Day, $Month, $Year, $Holiday
      $New_Msecond_100, $New_Msecond_250, $New_Msecond_500, $New_Second, $New_Minute,
      $New_Hour, $New_Day, $New_Month, $New_Year
      $Season, $Weekday, $Weekend, $Time_Of_Day, $Dark
      %Moon

    The complete list of global variables can be found on the Widgets menu
    of the default web page or at the top of mh/bin/mh.

    The %Moon array has $Moon{phase}, $Moon{brightness}, $Moon{age}, and
    $Moon{new,first,full,last} entries.

    The $Time_Now and $Date_Now variables return a formatted time and date.

    $Dark is true between $Time_Sunset_Twilight and $Time_Sunrise_Twilight,
    false otherwise.

    The $New_* variables are true only on the pass that we enter a new
    Second, Minute, Hour, etc. $New_Msecond_100/250 are true every .1 and
    .25 seconds. There are also new_* functions if you want to test on every
    nth second/minute/hour (see new_second function).

    $Startup is true on the first pass after starting mh.

    $Reload is true when the Reload(F1) is requested AND the code been
    reloaded because one or more code members have been changed.

    $Reread is always true after Reload(F1), even if no code members have
    changed.

    $Keyboard echoes characters typed on the keyboard (currently windows
    only)

    %Save is a hash in which the values of variables can be saved so that
    they remain unchanged after mh is re-started. The values can be checked
    using the web widgets menu.

    %Misc is a hash in which other miscellaneous data can be stored. These
    values can also be checked using the web widgets menu. This is a handy
    place to put data you might want to display in .shml web pages.

  List of objects and their methods
    File_Item
        You can use File_Item to read a line of data from a file. Note:
        These methods currently read the entire file, so if have big files
        (say, >1 meg) we want to read, we should invent some new methods.

          Methods:
             new('file_name')

             name         : Returns 'file_name'

             read_all     : Returns contents for the file. If used in a list context,
                            a list is returned, otherwise a string of all the lines.

             read_head    : Returns the first few lines of a file.  See file_head.
             read_tail    : Returns the last  few lines of a file.  See file_tail.

             read_random  : Reads a random record.
                            This also re-sets the index to the random position.

             read_next      : Reads the next record, according to the index.
                              After reading the last record, it wraps back to the first.
             read_next_tail : Like read_next, except, it will not wrap back to
                              the first record (i.e. after reaching the end of the file,
                              it will alwasy return the last record).
             read_current   : Reads the current record, according to the index.

             get_index    : Which record was last read.  A couple of notes:
                              - The index is saved between mh sessions.
                              - If you use a File_Item that does not yet have
                                an index set, a random index will be used and stored.
             set_index    : Set the index.

             set_watch('flag') : Sets the 'changed' time check.

             changed      : Returns 0 if the file was not changed since a set_watch call.
                            When the file changes:
                             - If 'flag' was specified in the new method, 'flag' is returned.
                             - Otherwise, it returns the number of seconds since a set_watch call.

             exist        : Returns 1 if the file exists
             exist_now    : Returns 1 if the file was created since the last exist_now test.

             said         : Returns data added to a file since the last call.
                            Only one record is returned per call.
                            This is useful for monitoring log files.
                            See mh/code/bruce/shoutcast_monitor.pl  for an example.

          Examples:
             $f_deep_thoughts = new File_Item("$Pgm_Root/data/remarks/deep_thoughts.txt");
             my $thought = read_next $f_deep_thoughts;
             set_index $f_deep_thoughts 1;

             $f_weather_forecast = new File_Item("$Pgm_Root/data/web/weather_forecast.txt");
             set_watch $f_weather_forecast;
             display name $f_weather_forecast if changed $f_weather_forecast;

             $shoutcast_log = new File_Item 'd:/shoutcast/sc_serv.log';
             print "Log data: $state" if $New_Second and $state = said $shoutcast_log;

    Generic_Item
        You can use this object to store and query arbitrary data. This is
        more useful than 'my' variables, if you need to share data between
        code files, since 'my' variables are local to a code file. States of
        these items are also saved/restored when mh is stopped/started.

        All other items that have states (e.g. X10_Item, Serial_Item,
        iButton, Voice_Cmd, Group) inherit all Generic_Item methods.

          Methods:
             new           :  Creates an object of the specified name

             state         :  Returns the state (e.g. on, off)
             state_now     :  True only for one pass after object state is set.
             state_changed :  True only for one pass after object state changes.
                              Unlike state_now, state_changed will return undef (false) if
                              the item is set to the same state it is currently in.
             state_final   :  Returns the state the object will be in after all queued
                              state changes have been processed.

             set($state)   :  Places the value into the state field (e.g. set $light on)
                              Note: states are set at the start of the next mh pass.
             set_now($state) :  Like set, except states are set when called, not on the next pass.
             set_with_timer($state, $time, $return_state)
                           :  Like set, but will return to $return_state after $time.
                              If $return_state is not specified, it toggles $state.
                              If $return_state is previous, it returns to the previous state.
                              If set is called before the timer expires, the timer will be unset.

                You can also stack a series of set_with_timer calls with one set call like this:

                   set('s1~t1~s2~t2...sn');

                where s1, s2, ... sn are various states, and t1, t2 ... tn are the times to
                wait between states.  See example below.

                If you want to stack a set of states without delays, you use ; like this:

                   set('on~2~random:on;repeat:on;play');

                See mh/code/examples/test_states_stacked.pl for a complete example.
                Note, this is turnded off by default for Serial_Item objects.
                To enable, run:   $object -> state_overload(ON);

             hidden(1/0)         : If set to 1, the object will not show up on Tk or Web menus.

             tie_items($item)    : If the state of the generic_item changes, then
                                   the state of $item will be set to that same state
             untie_items($item)  : Untie $item

             tie_event($code)   : It the state of the generic_item changes, then
                                   Code string $code will trigger, with the variable
                                   $state getting expanded.
             untie_items($code)  : Untie $code

             tie_filter($filter) : Use this to disable control of the item whenever
                                   $filter returns true.   Variables $state and $set_by
                                   can be used in the $filter test.

             untie_filter($filter):Untie $filter

             tie_time($time,$state): Sets item to $state if the $time string evaluates true.
                                   $time can be in either time_cron or time_now format.
             untie_time($time)   : Untie $time

             time_idle($time)    : Returns true when the object has had no state changes
                                   since the specified time. $time can be be in seconds,
                                   minutes, hours, or days (e.g. '90 s' or '7 days').
                                   Optionally, $time can also specify a spefic state (e.g. '4 m on')

             set_by              : allows setting a description of why the last state change
                                     - motion, sunrise, manual, serial,    etc..  any string
                                       is allowed value is returned by get_set_by below.

             get_idle_time       : Returns number of seconds since the last state change

             get_set_by          : Returns what caused this object to change.  Values either:
                                    - Name of the object
                                    - web, tk, telnet, vr, xcmd, serial
                                   An example is in mh/code/examples/test_set_by.pl

             set_states(@states) : Sets valid states to @states
             add_states(@states) : Adds @states to the list of valid states
             get_states          : Returns the list of valid states

             restore_data(@vars) : Specifies which variables should be saved/restored between
                                   mh reload/restarts.  The state var is always saved.

             set_authority('anyone') : Bypasses password control

             set_icon : Point to the icon member you want the web interface to use.
                        See the 'Customizing the web interface' section of this document for details.

             set_info : Adds additional information.  This will show up as a popup window
                        on the web interface, when the mouse hovers over the command text.

             set_label: Specify a text label, useful for creating touch screen interfaces.

             set_web_style: Contols the style of we form used.  Can be dropdown, radio, or url.
                            See mh/code/examples/test_web_styles.pl

             set_count   : Use these methods to control a count variable.
             get_count
             incr_count
             reset_count

        The tie/untie methods can be done conditionally, so you can change
        what is tied to what on the fly. If you do this, however, you need
        to also make sure you also make the tie* method call on $Reload, as
        the objects tie items/events are currently not stored between
        reloads.

        The tie_* methods can also take 2 optional arguments. The first is
        the state you want the tie to apply to. If not specified, it
        defaults to all states. This is required for tie_time.

        The 2nd optional tie_* argument is a print_log message you want
        whenever the tie_ event triggers.

          Examples:
             $tv_grid = new Generic_Item;
             set $tv_grid 'channel 2 from 7:00 to 8:00 on 1/24 for This Old House';
             speak "tv set to $state" if $state = state_now $tv_grid;

             $wakeup_time = new Generic_Item;
             speak "Your wakeup time is set for $state" if $state = state_now $wakeup_time;
             speak "Time to wake up" if time_now state $wakeup_time;

           # Since Voice_Cmd objects inherit Generic_Items methods, we can
           # use tie_items and tie_event like this:

             $indoor_fountain    = new X10_Appliance('OB');
             $v_indoor_fountain  = new Voice_Cmd 'Indoor fountain [on,off]';
             $v_indoor_fountain -> tie_items($indoor_fountain);
             $v_indoor_fountain -> tie_event('speak "Ok, fountain was turned $state"');

             if ($state = state_now $test_button) {
                my $ref = get_set_by $test_button;
                print_log "Test button's was set to $state by $ref->{object_name}" if $ref;
             }

                                   # This shows how to set states
            $TV  = new IR_Item 'TV';
            my  $tv_states = 'power,on,off,mute,vol+,vol-,ch+,ch-';
            set_states  $TV split ',', $tv_states;  # causes $tv_states to be split into array
                                                                   values via the split command deliminated by the ,
            $v_tv_control = new  Voice_Cmd("tv [$tv_states]");

                                   # This shows how to save persistant arbitrary data
            $Test = new Generic_Item;
            $Test->{junk_data1} = $Time_Now if $Reload;
            $Test->{junk_data2} = $Month    if $Reload;
            $Test->restore_data('junk_data1', 'junk_data2');

                                   # Here are some tie* examples
            $fountain -> tie_filter('state $windy eq ON', ON,
                                    'Overriding item1 ON command because of wind');

            $fountain -> tie_time('10PM', OFF, 'Fountain turned off');

                                   # Disable an item when we are away
            $item1 -> tie_filter('state $status_away eq ON');
            $item1 -> tie_event('print_log "item1 toggled to $state"');

                                   # Ignore RF sourced (e.g. W800 or MR26) X10 data
            $light1 -> tie_filter('$set_by eq "rf"', , 'Ignoring x10 rf data');

                                   # Disable callerid announcements when we are not at home
            $cid_announce -> tie_filter('state $mode_occupied ne "home"');

                                   # Set an item to multiple states, with 5 second a delay between states
            $test_set1  = new Generic_Item;
            $test_set1 -> set('0%~5~30%~5~60%~5~100%') if new_second 20;
            $test_set1 -> tie_event('print_log "test set $state"');

            print 'Item is idle' if $test_set1 -> time_idle('4 seconds');

        See mh/code/examples/generic_item.pl for more examples, test_tie.pl
        for more examples on how to tie/untie items/events, and test_idle.pl
        for more examples on testing idle times.

    Group
        You can use this object to group and operate on groups of items:

          Methods:
             new(@item_list)
             add(@item_list)
             list
             set

             state
             state_now    :  Like the Generic_Item methods, these return
                             the last state that the group was set to.
                             If a group member changed, these methods will
                             return 'member $state_name' rather than just '$state_name'.
                             You can use the member_changed or get_set_by methods
                             to see which member changed.

             state_log    : Returns a list array of the last max_state_log_entries (mh.ini parm)
                            time_date stamped states.

             member_changed:      Returns a member object name whenever one changes
             member_changed_log:  Returns a list of recenty changed members.
                                  The first one was the most recently changed.

             remove       : Remove an item from a group

          Examples:
             $outside_lights = new Group($light1, $light2, $light3);
             $outside_lights-> add($light4, $light5);
             $outside_lights-> add($light6);
             set $outside_lights ON if time_now("$Time_Sunset + 0:15");

             for my $item (list $outside_lights) {
                 print "member = $item->{object_name}\n";
             }

             if (my $member_name = member_changed $outside_lights) {
                my $member = &get_object_by_name($member_name);
                print_log "Group member $member_name changed to $member->{state}"
             }

             my @members = member_changed_log $sensors;

             # turn off all but the bedroom light when we are getting ready for bed
             if( state_now $bedroom_control eq ON ) {
                my $all = new Group(list $All_Lights);
                $all -> remove ( $master_bedroom );
                set $master_bedroom ON;
                set $all OFF;
             }

        See mh/code/examples/test_group.pl and
        mh/code/public/monitor_occupancy_jason.pl for more examples.

    iButton_Item
        This is used to query and/or control an iButton device (for more
        info on iButton, see the hardware section). To enable iButton
        support in mh, set the mh.ini parm ibutton_port.

          Methods:
             new $id, $port, $channel
               If $port is not specified, the port of the first iButton::connect
               will be used.
               $channel (used for switches like the DS2406) defaults to A

             set $state   : Sets the item to the specified state.

             state        : Returns the last state that was received or sent

             state_now    : Returns the state that was received or sent in the current pass.

             state_log    : Returns a list array of the last max_state_log_entries (mh.ini parm)
                            time_date stamped states.

             read_temp    : Returns the temperature of temperature devices.

             read_switch  : Reads iButton switch data

             read_windspeed : Reads iButton weather station wind speed
             read_dir       : Reads iButton weather station wind direction

        In addition to the above, all of the methods provided by the
        Hardware/iButton/Device.pm module are available (documented in
        mh/lib/site/Hardware/iButton/Device.pm).

        These functions are also part of the iButton module, but not
        associated with an object:

             scan        $family, $port : Returns a object list of iButton devices that match $family
             scan_report $family, $port : Returns a report of      iButton devices that match $family

             monitor $family, $port : Checks the one wire bus for iButton activity

             connect $port       : Connect to the one wire bus.  Note you can now have multiple
                                   iButton interfaces on multiple COM ports.

             disconnect          : Disconnect to the one wire bus

        Note, all of these functions take an optional $port parm (required
        for connect). If not specified, the port of the first connect record
        will be used.

        The $id required when creating new iButton_Item is the 16 hex
        character (64 bit) iButton id that is unique to every device. This
        is often printed on the larger devices. If not, you can use:

            $v_iButton_list      = new Voice_Cmd "List all the iButton buttons";
            print_log &iButton::scan_report if said $v_iButton_list;

        The last 2 characters are CRC bits and are optional. If missing
        (i.e. you only specify the first 14 characters), mh will calculate
        it.

        The first 2 characters are the iButton family type. Here is a list
        of family types:

             Field Index:
              ------------
              (1) Family code in hex
              (2) Number of regular memory pages
              (3) Length of regular memory page in bytes
              (4) Number of status memory pages
              (5) Length of status memory page in bytes
              (6) Max communication speed (0 regular, 1 Overdrive)
              (7) Memory type (see below)
              (8) Part number in iButton package
              (9) Part number in non-iButton package
              (10) Brief descriptions

              (1)   (2)  (3)  (4)  (5)  (6)  (7)   (8)   (9)   (10)
              -------------------------------------------------------
              01,    0,   0,   0,   0,   1,   0, DS1990A,DS2401,Unique Serial Number
              02,    0,   0,   0,   0,   0,   0, DS1991,DS1205, MultiKey iButton
              04,   16,  32,   0,   0,   0,   1, DS1994,DS2404,4K-bit NVRAM with Clock
              05,    0,   0,   0,   0,   0,   0, DS2405,,Single Addressable Switch
              06,   16,  32,   0,   0,   0,   1, DS1993,DS2403,4K-bit NVRAM
              08,    4,  32,   0,   0,   0,   1, DS1992,DS2402,1K-bit NVRAM
              09,    4,  32,   1,   8,   1,   2, DS1982,DS2502,1K-bit EPROM
              0A,   64,  32,   0,   0,   1,   1, DS1995,DS2416,16K-bit NVRAM
              0B,   64,  32,  40,   8,   1,   3, DS1985,DS2505,16K-bit EPROM
              0C,  256,  32,   0,   0,   1,   1, DS1996,DS2464,64K-bit NVRAM
              0F,  256,  32,  64,   8,   1,   3, DS1986,DS2506,64K-bit EPROM
              10,    0,   0,   0,   0,   0,   0, DS1920,DS1820,Temperature iButton with Trips
              11,    2,  32,   1,   8,   0,   2, DS1981,DS2501,512-bit EPROM
              12,    4,  32,   1,   8,   0,   4, DS2407,,Dual Addressable Switch
              13,   16,  32,  34,   8,   0,   3, DS1983,DS2503,4K-bit EPROM
              14,    1,  32,   0,   0,   0,   5, DS1971,DS2430A,256-bit EEPROM, plus
              64-bit
              OTP
              15,    0,   0,   0,   0,   1,   0, DS87C900,,Lock Processor
              16,    0,   0,   0,   0,   0,   0, DS1954,,Crypto iButton
              18,    4,  32,   0,   0,   1,   6, DS1963S,4K-bit Transaction iButton with
              SHA
              1A,   16,  32,   0,   0,   1,   6, DS1963,,4K-bit Transaction iButton
              1C,    4,  32,   0,   0,   1,   6, DS2422,,1K-bit EconoRAM with Counter
              Input
              1D,   16,  32,   0,   0,   1,   6, DS2423,,4K-bit EconoRAM with Counter
              Input
              1F,    0,  32,   0,   0,   0,   0, DS2409,,One-Wire Net Coupler
              20,    3,   8,   0,   0,   1,   9, DS2450,,Quad A-D Converter
              21,   16,  32,   0,   0,   1,   8, DS1921,,Temperature Recorder iButton
              23,   16,  32,   0,   0,   1,   7, DS1973,DS2433,4K-bit EEPROM
              22                                 DS1822 temperature button
              40,   16,  32,   0,   0,   0,   1, DS1608,,Battery Pack Clock

          Examples:

            $v_iButton_connect = new Voice_Cmd "[Connect,Disconnect] to the iButton bus";
            if ($state = said $v_iButton_connect) {
              if ($state eq 'Connect') {
                print_log &iButton::connect($config_parms{iButton_serial_port});
                print_log &iButton::connect($config_parms{iButton_2_serial_port});
              }
              else {
                print_log &iButton::disconnect;
                print_log &iButton::disconnect, $config_parms{iButton_2_serial_port};
              }
            }

            $ib_bruce  = new iButton '010000012345ef';
            speak 'Hi Bruce'  if ON  eq state_now $ib_bruce;
            speak 'Later'     if OFF eq state_now $ib_bruce;

            $ib_relay1 = new iButton '12000000123456ff', undef, 'A';
            $ib_relay2 = new iButton '12000000123456ff', undef, 'B';
            $v_iButton_relay1    = new Voice_Cmd "Turn relay1 [on,off]";
            if ($state = said $v_iButton_relay1) {
               print_log "Setting iButton relay1 to $state";
               set $ib_relay1 $state;
            }

            $ib_temp1  = new iButton '1000000029a14f', $config_parms{iButton_2_serial_port};
            $ib_temp2  = new iButton '1000000029f5d6';
            my @ib_temps = ($ib_temp1, $ib_temp2);

            $v_iButton_readtemp  = new Voice_Cmd "Read the iButton temperature [1,2]";
            if ($state = said $v_iButton_readtemp) {
               my $b = $ib_temps[$state-1];
               my $temp = read_temp $b;
               print_log "Temp for sensor $state: $temp F";
               logit("$config_parms{data_dir}/iButton_temps.log",  "$state: $temp");
            }
            if ($New_Second and !($Minute % 5)) {
               run_voice_cmd 'Read the iButton temperature 1' if $Second == 11;
               run_voice_cmd 'Read the iButton temperature 2' if $Second == 22;
            }

    HVweb_Item
        This object allows control of Homevision controller via the
        Homevision web server. Set homevision_url=<your homevision web
        server URL> in mh.ini. See the Homevision documentation for complete
        list of command formats. Configure Homevision Webserver to report
        command results.

          Examples:
             $kitchen_light    =  new HVweb_Item('On',    'X10 G1 On');
             $kitchen_light    -> add           ('Off',   'X10 G1 Off');
             $vcr              =  new HVweb_Item('Power', 'IR 45 1 time');
             $vcr              -> add           ('Play',  'IR 46 1 time');

             set $kitchen_light 'On';
             set $vcr 'Play';

    IR_Item
        This object controls IR transmiters. The devices currently supported
        are the X10 IR Commander, HomeVision, CPU-XA/Ocelot/Leopard, and
        UIRT2 (http://www.fukushima.us/UIRT2/).

        The X10 IR Commander (http://www.x10.com/products/ux17a_bj2.htm)
        receives commands from the wireless CM17 (firecracker) interface.
        Currently, you must use the X10 supplied software
        (http://www.x10.com/commander.htm and/or
        ftp://ftp.x10.com/pub/applications/commander/) to program the IR
        Commander to use the codes for your various remotes.

          Methods:
             new $type, $code, $interface, $mapref : Creates a new Item.

                $type is the type of device being controlled.  Default is TV.
                Here are the only valid types for the X10 IR Commander:
                   TV, VCR, CAB, CD, SAT, DVD

                $code specifies how numbers will be treated.  Default is 2digit.
                   noPad   : numbers are not modified
                   2digit  : single digits will be padded with a leading zero
                   3digit  : numbers will be padded to 3 digits with leading zeros
                   addEnter: adds an ENTER command after any numbers, to make
                             changing channels faster on devices that wait for a
                             timeout (e.g. Sony TVs).

                $interface is the transmitter to be used.  Default is cm17.
                   cm17    : X10 IR Commander
                   homevision: HomeVision
                   ncpuxa  : CPU-XA/Ocelot/Leopard
                   uirt2   : UIRT2 (http://www.fukushima.us/UIRT2/)
                   xAP     : Sends / receives data via the xAP protocol.

                $mapref is a reference to a hash that specifies commands to be
                   mapped to other commands.  This is useful for transmitters
                   like the Ocelot which use slot numbers instead of device and
                     function name pairs.  Default is a reference to a hash containing
                        ON  => POWER,
                        OFF => POWER
                     Which you would not want if you had descrete ON and OFF codes.

             state        : Returns the last state that was sent
             state_now    : Returns the state that was sent in the current pass.
             state_log    : Returns a list array of the last max_state_log_entries (mh.ini parm)
                            time_date stamped states.

             set $command : Sends out commands to the IR device.  Here is a list of valid commands for the X10 IR Commander::
                            Note:  Commands are not case insensitive
                POWER      MUTE
                CH+        CH-
                VOL+       VOL-
                1          2
                3          4
                5          6
                7          8
                9          0
                MENU       ENTER
                FF         REW
                RECORD     PAUSE
                PLAY       STOP
                AVSWITCH   DISPLAY
                UP         DOWN
                LEFT       RIGHT
                SKIPDOWN   SKIPUP
                TITLE      SUBTITLE
                EXIT       OK
                RETURN

          Examples:

            $TV = new IR_Item 'TV';
            $v_tv_control = new  Voice_Cmd("tv [power,on,off,mute,vol+,vol-,ch+,ch-]");
            set $TV $state if $state = said $v_tv_control;

            $VCR = new IR_Item 'vcr', '3digit';
            set $VCR "12,RECORD" if time_cron('59 19 * * 3');
            set $VCR "STOP"      if time_cron('00 20 * * 3');

    LCD This object is used to send and receive data to LCD type displays
        with keypads. An example is in mh/code/bruce/lcd.pl. To use simulate
        an LCD keypad with your pc keyboard, use
        mh/code/bruce/lcd_keyboard.pl.

          Methods:
             new $type, $port, $size, $menu_group, $keymap

               $type:  Either lcdproc or keyboard.
               $port:  The ip:port of where lcdproc is running.
               $size:  ROWSxCOLUMNS of the LCD display.
               $menu_group:  The menu parsed by menu_parse (see Menu section of this doc).
               $keymap: A has that translates keys to usable names.

             start        => Connect to the LCD.  Automatically called on startup
             stop         => Disconnect the LCD.
             load $menu   => Load menu $menu.  Default menu is the first one.
             check_key    => Returns whatever key is keyed in
             set @data    => Sends @data to the LCD, one line per list element
             set_key $key => Simulates the keyboard being pressed with $key
             inactive     => Returns true if no key has been pressed in 10 seconds.

          Examples:

             my %lcd_keymap1 = ( N => 'up', I => 'down', M => 'left', H => 'right',
                                 F => 'exit', K => 'enter', L => 'left', G => 'right');
             my %lcd_keymap2 = ( 38=> 'up', 40=> 'down', 37=> 'left',
                                 39=> 'right', 17=> 'exit', 96=> 'enter') ;

            $lcd1 = new LCD  'lcdproc', '192.168.0.5:13666', '4x20', 'default', \%lcd_keymap1;
            $lcd2 = new LCD 'keyboard',               undef, '4x20', 'mh',      \%lcd_keymap2;

    Process_Item
        You can use this object to run external programs. On Win32 systems,
        the Win32::Process function is used. On Unix systems, the fork
        function is used. On either system, the following methods work in
        the same way:

          Methods:
             new('program1 arguments', 'program2 arguments', ...)
             set('program1 arguments', 'program2 arguments', ...)
             add('program3 arguments', 'program4 arguments', ...)

                If you specify more than one program, they are run sequentially.  done_now returns
                1 after the last program is done.

                If program starts with &, then 'program arguments' is eval-ed as an internal mh function.
                Otherwise, 'program arguments' is run as an external command.

                On Windows, the &-> eval trick is supposed to work with perl 5.6+ (which has fork), but
                unfortunately, it causes perl to crash often, so is probably not useful yet.

             set_timeout $timeout     : Process will timeout after $timeout seconds
             set_output  $output_file : Program STDOUT errata goes to $output_file
             set_errlog  $errlog_file : Program STDERR errata goes to $errlog_file

             start          : Starts the process.
             start 'program arg'  : Starts the process with program arg

             stop           : Stops the process. If called as a stand alone function
                              (not as an object method), all active Process_Items are stopped.

             done           : Returns the time (seconds since epoch) that the process finished.  If
                              the process has been started, but has not yet finished, it returns 0.

             done_now       : Is true for the pass that the process finished on.

             pid            : Returns the process id

             timed_out      : Returns the time when the process timed out.
                              done_now will still trigger for a timed_out process.

          Examples:
             my $slashdot_news = "$Pgm_Root/data/web/slashdot_news.txt";
             $p_slashdot_news = new Process_Item("get_slashdot_news > $slashdot_news");
             start $p_slashdot_news if time_now('6:30 AM');
             display $slashdot_news if done_now $p_slashdot_news;

             $p_report_weblog = new Process_Item;
             $p_report_weblog ->set_output("$config_parms{data_dir}/weblog_results.txt");
             if (time_now '2 AM') {
                 set   $p_report_weblog "report_weblog /mh/data/logs/server.$Year_Month_Now.log";
                 start $p_report_weblog;
             }

                            # Example of multiple commands
             $test_process1 = new Process_Item;
             set $test_process1 'sleep 1', 'sleep 2';
             add $test_process1 'sleep 1';

                            # Example of running an internal mh subroutine
            $v_test_ftp = new Voice_Cmd 'Test background ftp [get,put]';
            $p_test_ftp = new Process_Item;
            if ($state = said $v_test_ftp) {
              set $p_test_ftp "&main::net_ftp(file => '/tmp/junk1.txt', " .
                              "file_remote => 'incoming/junk1.txt'," .
                              "command => '$state')";
              set_timeout $p_test_ftpb 60*2;
              start $p_test_ftpb;
            }
            print_log "Ftp command done" if done_now $p_test_ftp;

        More examples are in mh/code/examples/test_process.pl

    RF_Item
        An RF_item is created to receive states from X10 security devices
        and RF TV style remote through a W800RF32 module or an MR26A module
        (the MR26A only passes TV remote style data through, it does not
        pass security data).

        To configure the W800 or MR26 interfaces, see the comments at the
        top of mh/lib/X10_MR26.pm and mh/lib/X10_W800.pm.

        RF items can be created in the items.mht in the following manner:

             RF,     68,     keychain_remote,                Security
             RF,     System, security_system,                Security
             RF,     Sensor, security_sensors,               Security
             RF,     81,     door_sensor,                    Security
             RF,     Remote, tv_remote,                      TV

        RF Items can be manually created in the following manner:

             $keychain_remote  = new RF_Item('68'    , 'keychain_remote' );
             $security_system  = new RF_Item('system', 'security_system' );
             $security_sensors = new RF_Item('sensor', 'security_sensors');
             $door_sensor      = new RF_Item('81'    , 'door_sensor'     );
             $tv_remote        = new RF_Item('remote', 'tv_remote'       );

        The 2nd column the items.mht file (or the 1st parameter when
        manually creating a new RF_Item) is the 2 digit hexadecimal unit id
        of the particular transmitter or one of the following classes:

             system     Any device that change the state of the security system.
                        States from any transmitters that go into this class are:
                        armawaymin, armawaymax, armhomemin, armhomemax, disarm, panic
             sensor     Any device that changes a sensor state.
                        States from any transmitters that go into this class are:
                        normal, normalmax, normalmin, alert, alertmin, alertmax
             control    Any device that changes some general feature:
                        States from any transmitter that go into this class are:
                        lightson, lightsoff
             remote     Any TV style remote control (UR51A, etc.).
                        States from any transmitter that go into this class are:
                        Power PC Title Display Enter Return Up Down Left Right Menu
                        Exit Rew Play FF Record Stop Pause Recall 0 1 2 3 4 5 6 7 8 9
                        AB Ch+ Ch- Vol- Vol+ Mute

        Some transmitters have a min and max switch that cause the
        transmitter to send different states depending on that switch. If
        you don't care about the full detail of the state, you can do a test
        like:

            if (my $state = state_now $door_sensor =~ /^alert/) { ... }

        To determine what the 2 digit hexadecimal unit id for a particular
        security transmetter is, press the button on the transmitter (or
        open/close the sensor) and look at the misterhouse log to find the
        id that the unit transmitted.

    Serial_Item
        Serial_Item is used whenever you want read or write serial port
        data. Like most mh objects, it inherits all the Generic_Item
        methods.

          Methods:
             new('data_stream', 'state_name', 'serial_port')
             add('data_stream', 'state_name')

                If 'serial_port' is not specified, then:
                   For outgoing X10 data, the first valid port from this list is used:
                       cm11, cm17, homevision, homebase, Weeder, serial1, serial2, ...
                   For other outgoing data, the first valid port from this list is used:
                       Weeder, serial1, serial2, ...

                   For incoming data, the 'serial_port' parm is not important, as
                   data from the cm11, homebase, and Weeder ports is processed in the same way.

                   For the generic ports (serial1, serial2, ...), incoming data is processed
                   only by user specified 'said' methods.

             state        : Returns the last state that was received or sent

             state_now    : Returns the state that was received or sent in the current pass.
                            Note that the a 'set' will trigger a state_now one the next pass.

             state_log    : Returns a list array of the last max_state_log_entries (mh.ini parm)
                            time_date stamped states.

             set_dtr      : Sets/resets the DTR line
             set_rcs      : Sets/resets the RCS line.  For example:

                              set_dtr $port 1;
                              set_rcs $port 0;

             set          : Sets the item to the specified state.
                             - If the specified state has not been defined with 'new' or 'add',
                               the state data is sent.  Otherwise the data_stream associated
                               with that state is sent.

             set_data     : Use this to put data back into the serial read buffer.  Typically used
                            when you have processed only some of the serial data, and you want to
                            put the rest back because it is incomplete.  Here is an example:

                           if (my $data = said $wx200_port) {
                              my $remainder = &read_wx200($data, \%weather);
                              set_data $wx200_port $remainder if $remainder;
                           }

             said         : Returns a data record received by the port.
                             - Characters are spooled into one record until a '\n' (newline) is
                               received.  Only then does 'said $item' become valid, and it is
                               reset as soon as the said method is executed.

                             - Note: If you want to process binary serial data, specify
                                     serial#_datatype=raw in the mh.ini file.  This
                                     will cause said to return any data read immediately,
                                     rather than buffering up data until a newline is read.

             start        : Re-starts the serial port after a stop command or after starting
                            with the port already in use.

             stop         : Stops using the serial port for this item.  That allows other programs
                            to share the port (e.g. let mh use the modem for caller ID, but turn
                            that function off when using the modem to call out).

             is_stopped   : True if the port is not active
             is_available : True if the port is not in used by another program

             set_icon : Point to the icon member you want the web interface to use.
                        See the 'Customizing the web interface' section of this document for details.

             set_info : Adds additional information.  This will show up as a popup window
                        on the web interface, when the mouse hovers over the command text.
                        will show up as a popup window on the web interface,
                        when the mouse hovers over the command text.

          Examples:
             $garage_movement = new Serial_Item('XI2');
             speak "Someone is in the garage" if state_now $garage_movement;

             $tnc_output = new Serial_Item ('CONV', 'converse', 'serial1');
             $tnc_output -> add            ('?WX?', 'wxquery');
             set $tnc_output 'converse';
             set $tnc_output "EMAIL    :userid\@computers.com Test E-Mail - $CurrentTemp deg.{0";
             my $serial_data = said $tnc_output;

             $v_tnc_close = new  Voice_Cmd("close the tnc serial port");
             stop  $tnc_output if said $v_tnc_close;
             start $tnc_output if $New_Minute and is_stopped $tnc_output
                                  and is_available $tnc_output;

        See mh/code/examples/serial_port_examples.pl for more Serial_Item
        examples.

    Weather_Item
        This object can be used to track data stored in the global %Weather
        array.

          Methods:
             new $type
                $type is the name of the %Weather index you want to monitor

                $type can also have a =<> comparison operator in it so
                you can make the object a true/false test.

             state        : Returns the last state, or 1/0 if $comparition and $limit were used.
             state_now    : Returns the state only when the weather data changed.

             It also inherits the Generic_Item methods.

          Examples:

           $WindSpeed = new Weather_Item 'WindSpeed';
           $WindSpeed-> tie_event('print_log "Wind speed is now at $state"');

           $freezing = new Weather_Item 'TempOutdoor < 32';
           if (state_now $fountain eq ON and state $freezing) {
             speak "Sorry fountains don't work too well when frozen";
             set $fountain OFF
           }

           $Windy = new Weather_Item 'WindSpeed > 15';
           speak "Wind is gusting at $Weather{WindSpeed} mph" if state $Windy;

                          # For current state, easiest to use it directly
           speak "Outdoor temperature is $Weather{TempOutdoor} degrees";

        For examples on interface code that stores data into %Weather, see
        mh/code/bruce/weather_monitor.pl (uses mh/lib/Weather_wx200.pm),
        mh/code/public/iButton_ws_client.pl, and
        mh/code/public/weather_com.pl

    X10_Item
        Same as Serial_Item, except the 'X' prefix is prepended, to indicate
        an X10 command, and it has pre-defined states 'on', 'off'.
        'brighten', 'dim', and '+5'->'+95 and '-5'->'-95'. The +-## states
        increase/decrease brightness by ## percent.

        You can specify a 2nd argument, if you are using more than one X10
        interface and you want to control an X10_Item with a specific
        interface. For example, if you want to control the local module on a
        RF Transceiver, you can tell mh to use the RF CM17 interface, like
        this:

            $test_light = new X10_Item('A1', 'CM17');

        Note that the X10 device code is numbered 1->9,A->G. For example
        device 16 in house code P would be PG.

        If a single character is use (e.g. X10_Item 'D'), then 'on' and
        'off' states are translated to ALL_ON and ALL_OFF commands for that
        house code. For example:

           $v_test_lights = new Voice_Cmd 'All lights [on,off]';
           $test_lights   = new X10_Item 'O';
           set $test_lights $state if $state = said $v_test_lights;

        Since this item is inherits from Generic_Item, you can use the
        set_with_timer method. For example, this event will turn on a on a
        warning light to 20% for 5 seconds:

           set_with_timer $watchdog_light '20%', 5 if file_unchanged $watchdog_file;

        After doing one or more bright/dim/on/off commands, you can query
        the current brightness level of a device with the level method. For
        example:

           if ($state = state_now $pedestal_light) {
              my $level = level $pedestal_light;
              print_log "Pedestal light state=$state, level=$level"
           }

        If you have one of the newer (more expensive) LM14A/PLM21 2 way X10
        pro lamp modules, you can tell mh to send an extended X10 codes that
        let them be set directly to a specific brightness level using a
        Preset Dim extended code. The 64 extened X10 Preset Dim codes can
        used most easily by using the '##%' state. Note this differs from
        the +-# states described above, which increase/decrease brightness
        relative to the current state. The ##% state goes directly to that
        level, independent of the existing state.

        If you have a LM14 lamp module (or any other module that supports
        Preset Dims), you can use the ##% preset dim states. Set the 3rd
        X10_Item parm to 'preset' or 'LM14' to specify you are using a
        module that supports preset dims. For example:

          $test_light2 = new X10_Item('O7', undef, 'preset');
          $v_test_light2 = new Voice_Cmd("Set test light to [on,off,bright,dim,5%,10%,20%,30%,40%,50%,60%,70%,80%,90%]");
          set $test_light2 $state if $state = said $v_test_light2;

        You can also use them directly, using &P## (## = 1->64) as shown in
        this example:

          $test_light1 = new X10_Item('O7', 'CM11', 'LM14');
          $v_test_light1 = new Voice_Cmd("Set test light to [on,off,bright,dim,&P3,&P10,&P30,&P40,&P50,&P60]");
          set $test_light1 $state if $state = said $v_test_light1;

        The older X10 modules can only be brightened/dimed relative to their
        current state. If you specify a specific brightness level with ##%
        (e.g. 10% or 90%) when using an older X10 module, mh will use what
        it thinks the current brightness level is to come up with a +-##
        bright/dim command. If the module was off, it will first be turned
        to full on, since the older modules can not be dimmed from an off
        state.

        There is another set of Preset Dim commands that are used by some
        modules (e.g. the RCS TX15 thermostate). These 32 non-extended
        Preset Dim codes can be coded directly, using the following table:

          0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15   PRESET_DIM1
          M  N  O  P  C  D  A  B  E  F  G  H  K  L  I  J

          16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31  PRESET_DIM2
          M  N  O  P  C  D  A  B  E  F  G  H  K  L  I  J

        For example:

          $TX10 = new Serial_Item('XM4' . 'E' . 'PRESET_DIM1', 'Increase temp');  #preset  8='E'
          $TX10 -> add           ('XM4' . 'F' . 'PRESET_DIM1', 'Decrease temp');  #preset  9='F'
          $TX10 -> add           ('XM4' . 'O' . 'PRESET_DIM2', 'Preset on');      #preset 18='O'
          $TX10 -> add           ('XM4' . 'P' . 'PRESET_DIM2', 'Preset off');     #preset 19='P'

    X10_Appliance
        Same as X10_Item, except it has only has pre-defined states 'on' and
        'off'

    X10_Garage_Door;
        For the Stanley Garage Door status transmitter. See
        mh/code/public/Danal/Garage_Door.pl

    X10_IrrigationController;
        For this sprinkler device:
        http://ourworld.compuserve.com/homepages/rciautomation/p6.htm which
        looks the same as the IrrMaster 4-zone sprinkler controller listed
        here:
        http://www.homecontrols.com/product.html?prodnum=HCLC4&id_hci=0920HC
        569027

    X10_Switchlinc;
        For the Switchlinc controllers:

             # Just picked this device to use to send the clear
          $Office_Light_Torch->set("clear");
             # Send a command to each group member to make it listen
          $SwitchlincDisable->set("off");
             # Just picked this device item to send the command
          $Office_Light_Torch->set("disablex10transmit");

    X10_Ote;
        This supports the OTE X10 themostat from Ouellet Canada.

    X10_TempLinc
    X10_Sensor
        Do you have any of those handy little X10 MS12A battery-powered
        motion sensors? Here's your answer - use the X10_Sensor instead of
        the Serial_Item when you define it, and your house will notice when
        your sensor hasn't been tripped in 24 hours, allowing you to check
        on the batteries.

        If you have an sensor that detects and sends codes for daytime and
        nighttime (light and dark levels), pass in a optional type MS13,
        Motion, or Brightness. For the id, you can use the 2 character, or 5
        character X10 code. Here are some examples:

          $sensor_hall = new X10_Sensor('A4', 'sensor_hall', 'MS13');
          $work_room_motion = new X10_Sensor('CA', 'work_room_motion', 'motion');
          $work_room_bright = new X10_Sensor('CB', 'work_room_bright', 'brightness');

         .mht table examples:

           X10MS,      XA2AJ,  sensor_bathroom,       Sensors|Upstairs
           X10MS,       A4,    sensor_hall,           Sensors|Downstairs,  MS13

           X10MS,      CA,    work_room_motion,       Sensors|Motion_Sensors,      Motion
           X10MS,      CB,    work_room_brightness,   Sensors|Brighness_Sensors,   Brightness
           X10MS,      CA,    work_room_sensors,      Sensors,                  MS13    # This detects both motion and brightness

        With MS13 specified, it will return states named motion,still,light,
        and dark. With Motion specified, it will return only the motion and
        still states. With Brightness specified, it will return only the
        light and dark states. In all cases, methods light and dark will
        return the current light/dark state.

        Examples:

         set_with_timer  $light1 ON, 600 if $work_room_motion eq motion;

         speak 'It is dark downstairs' if dark $sensor_downstairs;

        Without the MS13 or Brightness type, the light/dark codes will be
        ignored.

    X10_Transmitter
        Like an X10_Item, but will not be set by incoming X10 data.
        Simulates transmit only devices like keypads. Can be used in place
        of X10_Item if you have complicated code that might get into a loop
        because we are not ignoring incoming X10 data for transmit-only
        devices.

    RCS_TX15
        Craig is working on this one.

    Socket_Item
        You can use the Socket_Item object to read and/or write to TCP/IP
        socket ports.

        Server socket ports are specified with mh.ini parms (see 'server
        options' in mh.ini)

          Methods:

             new('data_stream', 'state_name', 'host_port', 'name', 'host_protocol', 'datatype', 'break_char')
             add('data_stream', 'state_name')

                'host_port' must be of the form ip_address:port (when creating a client) or
                it must match a server_* parm in mh.ini (when creating a server) (e.g. server_telnet_port).

                'host_protocol' can be 'tcp' or 'udp'.  Default is 'tcp'

                'datatype'      can be 'raw', 'rawout', or 'record'.  If 'raw', the said
                                method returns data as read.  If record,
                                said only returns data when reaching a newline.
                                When writing to the port, a newline is added unless
                                datatype is 'raw' or 'rawout'.

                'break_char'    This will be added to data sent with set and, unless datatype=raw,
                                will be used to break incoming data into state_now records.
                                Default is \n\r.

                'name' is optional.  If used you can set debug to this string
                to turn on debug for just this socket data.

             is_available : Returns 1 if the socket is available, 0 if not

             set_port     : Allows you to change the server_name/port.

             state        : Returns the last state that was received or sent

             state_now    : Returns the state that was received or sent in the current pass.

             set          : Sets the item to the specified state.
                             - If the specified state has not been defined with 'new' or 'add',
                               the state data is sent.  Otherwise the data_stream associated
                               with that state is sent.
                             - If there is more than one client connected to this port, an
                               optional 2nd parm can be used to pick which client set to.
                               Values are:
                                  - 'all' (send to all connected clients),
                                  - ip_address (a regular expresion of the ip address(s) you want to send to
                                  - client_number (which client to send to, 0 .. #).  Note this
                                    can change as clients are added and dropped.
                                  - socket hash name, which can be captured when reading
                                    data from the port with this:
                                       $client = $Socket_Ports{$port_name}{socka};

             set_echo     : Use this to control echoing of incoming characters:
                             set_echo $my_socket 0   -> Do not echo incoming characters
                             set_echo $my_socket 1   -> echo incoming characters
                             set_echo $my_socket '*' -> echo incoming characters with *

             set_expect   : Used to send a series of strings to the port, waiting for
                            a specified prompt for each string.  This is useful for
                            walking through menus.  The arguments passed to set_expect
                            are pairs of 'prompt' => 'response' strings.  See example below.

             said         : Returns a data record received by the port.
                             - Note: If you want to process binary socket data, specify
                                     server_*_datatype = raw in the mh.ini file.  This
                                     will cause said to return any data read immediately,
                                     rather than buffering up data until a newline is read.

             said_next    : Reads and returns the next record from the socket handle

             handle       : Returns the socket handle, so you can loop on reading/writing data
                            to it directly.

             start        : Connect to the specified port.  This allows mh to act as a client,
                            rather than a server, and initiate communications with a server.

             stop         : This will drop the client or server from mh and free up the port.

             active       : True if the port is active
             active_now   : True for the mh pass that the socket first becomes active.
             inactive_now : True for the mh pass that the socket first becomes inactive.

          Examples:

                          # An example of a server socket
                          # Add this mh.ini parm:  server_speak_port = 8090
            $socket_server = new  Socket_Item(undef, undef, 'server_speak');
            if (my $data = said $socket_server) {
               print "speak_server socket data: $data\n";
               speak $data;
            }

                          # An example of a client socket
            my $lcdproc_address = 'misterhouse:13666';
            $lcdproc = new  Socket_Item(undef, undef, $lcdproc_address, 'lcdproc');

            $Vcmd_viavoice = new  main::Socket_Item(undef, undef, 'localhost:3234', 'viavoice');

                                         # This example walks the reboot menu of a router
            $router_reboot = new Voice_Cmd 'Reboot the router';
            $router_client = new Socket_Item(undef, undef, $config_parms{router_address} . ":23",
                                             'router', 'tcp', 'raw');
            set_expect $router_client (Password => $config_parms{router_password}, Number => 24,
                                       Number => 4, Number => 11) if said $router_reboot;

                          # Example of sending a message to all clients
            set $telnet_server "Hi to all clients", 'all';

        More examples are in mh/code/bruce/telnet.pl,
        mh/code/examples/socket_test*.pl,
        mh/code/bruce/monitor_shoutcast.pl, bruce/wxserver_server.pl, and
        bruce/monitor_router.pl

    Text_Cmd
        Use this object if you want to fire events based on text entered.
        Unlike the Voice_Cmd item, you can use Text_Cmd to capture arbitrary
        text, using a regular expression.

        Like Voice_Cmd items, all text passed to the run_voice_cmd and
        process_external_command functions will be tested against all
        Text_Cmd items. All items that match will fire their state_now
        methods.

          Methods:

             new($re_string);

               $re_string is any valid regular expresion.  Use the () grouping to
               pick the data that will be returned with the state_now method.

             state      : Returns the text from the () match in the $re_string.
                          If there was not () grouping, it returns 1.
                          If there is more than one () grouping, the resulting
                          matches are concatonated together with | as a separator.

             state_now  : Returns the state that was received or sent in the current pass.

             Like most items, this inherits all Generic_Item methods.

          Examples:

                            # Create a widget for inputing text commands
           $Text_Input = new Generic_Item;
           &tk_entry("Text Input", $Text_Input, "tcmd1", $tcmd1);

           if ($state = state_now $Text_Input) {
              my $set_by = get_set_by $Text_Input;
              print_log "Text_Input set_by $set_by typed $state";
              run_voice_cmd($state, undef, $set_by);
           }
                            # Create commands
           $tcmd1 = new Text_Cmd('hi (a|b|c)');
           $tcmd2 = new Text_Cmd('bye *(.*)');
           $tcmd3 = new Text_Cmd('(hi.*) (bye.*)');

                            # Fire events if the commands match text input
           $tcmd1->tie_event('print_log "tcmd1 state: $state"');
           print_log "tcmd2 state=$state" if $state = state_now $tcmd2;
           print_log "tcmd3 state=$state set_by=$tcmd3->{set_by}, target=$tcmd3->{target}" if $state = state_now $tcmd3;

    Timer
        The Timer object can be used to run an action one or more times, at
        a specified interval.

          Methods:

           These methods apply to a count down timer

            new        : Used to create the object.

            set($period, $action, $cycles)
                $period is the timer period in seconds
                $action (optional) is the code (either a string or a code
                         reference) to run when the timer expires
                $cycles (optional) is how many times to repeat the timer.
                         Set to -1 to repeat forever.

            unset      : Unset the timer.  'set $my_timer 0' has the same effect.

            active     : Returns true if the timer is still running.
            inactive   : Returns true if the timer is has expired or has not been set.
            expired    : Returns true for the one pass after the timer has expired.

            run_action : Runs the timers action, even if the timer has not expired.

            hours_remaining,   hours_remaining_now
            minutes_remaining, minutes_remaining_now
            seconds_remaining, seconds_remaining_now

                These methods return the hours, minutes or seconds remaining on the timer.
                The _now methods only return the remaining time on the hour, minute,
                or second boundary.

           These methods apply to using it as a count up, or stopwatch, timer

            start      : Starts the timer
            restart    : Restarts the timer (start on an active timer does nothing)
            stop       : Stops a timer
            pause      : Pauses
            resume     : Bet you can guess :)
            query      : Returns the seconds on the timer.

          Examples:

            $timer_laundary = new  Timer;
            $v_laundary_timer = new  Voice_Cmd('Laundary timer [on,off]');
            if ($state =  said $v_laundary_timer) {
               if ($state eq ON) {
                  play('rooms' => 'shop', 'file' => 'cloths_started.wav');
                  set $timer_laundary 35*60, 'speak "rooms=all The laundary clothes done"', 4;
               }
               else {
                  speak 'rooms=shop The laundry timer has been turned off.';
                  unset $timer_laundary;
               }
            }

                           # This example uses an anonymous subroutine
            $v_test_delay = new  Voice_Cmd 'Test timer with [1,5,10] seconds';
            if ($state = said $v_test_delay) {
               print_log "Starting $state second timer test";
               my  $timer = new Timer;
               set $timer $state, sub {
                  print_log "Ending $state second timer test";
               }
        #      set $timer $state, "print_log 'Ending $state second timer test'";
            }

            See mh/code/bruce/timers.pl for more examples

    Voice_Cmd
        Use the Voice_Cmd object to create voice commands. Even without a
        Voice Recognition engine installed, this is useful as these commands
        can also be run from the Tk, web, telnet, and file interfaces.

          Methods:
             new($command, $response, $confirm, $vocabulary)

                $command can be a simple string (e.g. 'What time is it') or it can
                include a list of 'states' (e.g. 'Turn the light [on,off]').  The state
                enumeration group is a comma delimited string surrounded with [].

                In addition to one state enumeration group, you can specify any number
                of phrase enumeration groups.  These are comma delimited strings
                surrounded by {} (e.g. 'Turn the {family room,downstairs} TV [on,off]').
                Use this when you have several different ways to describe the same thing.

                $response is the text or wave file that will be played back when the
                VR engine detects this command.  If not defined, the mh.ini parm
                voice_cmd_response parm is used (default is "Ok, %HEARD%").

                You can put %STATE%, %HEARD%, or any variable in the response string and
                have it substituted/evaluated when the response is spoken.

                $confirm is either 0 or 1 (default is 0).  If set to 1, then mh will
                ask 'Confirm with a yes or a no'.  If yes or no is not heard within
                10 seconds, the command is aborted.

                $vocabulary allows you to define multiple vocabularies.  You
                can then use these functions to enable and disable the vocabularies:

                     &Voice_Cmd::enablevocab($vocabulary)
                     &Voice_Cmd::disablevocab($vocabulary)

                Vocabularies are enabled by default.  The default vocabulary
                is 'misterhouse'.  See mh/code/bruce/viavoice_control.pl for examples.
                This code allows you to switch between 'awake', 'asleep', and 'off' VR modes.

                NOTE:
                   Currently only the viavoice VR engine (mh.ini parm voice_cmd=viavoice)
                   will use the $response, $confirm, and $vocabulary_name options.
                   We may be able to create a viavoice_server for windows, but that would
                   probably not be free like it is on linux.  If you have a linux box
                   on your network, you can have your windows mh use the linux
                   viavoice_server process.

             said  : Is true for the one pass after the command was issued.
                     If the command was built from a list of possible states,
                     then said returns the state that matches.

             state : Returns the same thing as said, except it is valid for all passes,
                     not just the pass after the command was issued.

             set_icon : Point to the icon member you want the web interface to use.
                        See the 'Customizing the web interface' section of this document for details.

             set_order: Contols the order that the commands are listed in web Category list.
                        The default is alphabetically by file, then by name.

          Examples:
            $v_backyard_light = new  Voice_Cmd 'Backyard Light [on,off]';
            set $backyard_light $state if $state = said $v_backyard_light;

            $v_test1 = new Voice_Cmd '{turn,set} the {living,famliy} room {light,lights} [on,off]';
            $v_test2 = new Voice_Cmd '{Please, } tell me the time';
            $v_test3 = new Voice_Cmd '{What time is it,Tell me the time}';
            $v_fan   = new Voice_Cmd 'Fan [on,off]', 'Ok, I turned the fan $v_indoor_fountain->{said}';
            $v_fan   = new Voice_Cmd 'Fan [on,off]', 'Ok, I turned the fan %STATE%';

        See mh/code/examples/Voice_Cmd_enumeration.pl for more Voice_Cmd
        examples

        In addition to the said command on a specific object, you can use
        &Voice_Cmd::said_this_pass to detect which command was spoken this
        pass and &Voice_Cmd::noise_this_pass to detect if noise was detected
        this pass (this function currently only works with viavoice).

          Examples:

           if (my $speak_num = &Voice_Cmd::said_this_pass) {
              my $text = &Voice_Cmd::text_by_num($speak_num);
              print_log "spoken text: $speak_num, $text";
           }

           if (my $text = &Voice_Cmd::noise_this_pass) {
              print_log "Noise detected" if $text eq 'Noise';
           }

        See mh/code/bruce/lcdproc.pl for more examples.

    DSC_Alarm
        DSC_Alarm module supports the DSC PC5400 serial printer interface.
        This allows mh to be aware of events that DSC alarm systems log to
        their event buffers.

          Required mh.ini entries:
            DSC_Alarm_serial_port=COM1 or /dev/ttys0
            DSC_Alarm_baudrate=4800

          Multiple instances may be supported by adding instance
          numbers to the parms as in:
            DSC_Alarm:1_serial_port=COMx or /dev/ttysX
            DSC_Alarm:1_baudrate=4800
            DSC_Alarm:2_serial_port=COMy or /dev/ttysY
            DSC_Alarm:2_baudrate=4800

          Optional mh.ini entries:
            DSC_Alarm_user_40=Jane Doe
            DSC_Alarm_user_1=Bob Smith

            See the "user" method below for a description.

          Methods:
             new('alarm-name')

                Where 'alarm-name' is the prefix used in the mh.ini entry 'DSC_Alarm_serial_port=xyz'.
                The 'alarm-name' argument defaults to 'DSC_Alarm' if not specified.

             said         : Returns the last serial data received.  Valid for 1 pass only.

             Important Note:  Due to mh internals, the "said" method and the "state" method (and
             all "state" derived methods) lag each other's values by 1 pass through the user scripts.
             As such, any given script should use "said" or "state", but should NOT mix the two!

             state        : Returns last state of alarm system from following values:
                            Armed    = System is closed and armed.
                            Disarmed = System is opened.
                            Alarm    = System is alarming.

             state_now    : Same as state, but valid for 1 pass only.

             user         : User number of last code used to arm/disarm system.

                            If present, mh.ini parm DSC_Alarm_user_nn=xyz will cause "user"
                            to return string "xyz" from the parm.

             alarm_now    : True when system enters Alarm state.  Valid for 1 pass only.

             zone         : Zone number that caused Alarm. Valid only when alarm_now is true.

             mode         : Returns arming mode. Valid only when state = Armed.
                            Stay = System armed in stay mode; User pressed F1 key before arming.
                            Away = System armed in away mode; User pressed F2 key (or nothing) before arming

                            Note: Most DSC systems will not arm in "Stay" mode unless at least one zone
                                  is defined as a "Stay/Away" zone.  Also, even when "Away" mode is requested
                                  system will be in "Stay" mode unless a delay zone is violated during the
                                  exit delay.

             Most other Generic_Item methods, such as "state_log", are valid.

          Logging:
             The internal support module for DSC_Alarm (DSC_Alarm.pm) maintains a log of
             all serial data received from the DSC PC5400 interface.  This log is placed
             in /mh.ini parm data_dir/logs/$port_name.YYYY_MM.log; for example, the log
             entries shown below would be in file '/mh/data/logs/DSC_Alarm.2000_10.log'.
             This implies a new log will be started each month.

          DSC User Codes:
             40 = Master code (can arm/disarm, change codes, any keypad function)
             41 = Supervisor code (can arm/disarm, change codes)
             42 = Supervisor code (can arm/disarm, change codes)
             01-32 = User codes (can arm/disarm, can be associated to individual wireless keys)
             33 = Duress code (can arm/disarm + sends duress code to master station)
             34 = Duress code (can arm/disarm + sends duress code to master station)

             The above information derived from PC1555 master panel; please see the installer
             manual for your particular panel for further information.

             Duress code reporting is NOT reflected via states as of December 2000.
             Coming soon...

          Examples:
            mh.ini entry of 'DSC_Alarm_serial_port=com2'

            $DSC_Alarm = new DSC_Alarm;
            if (my $log = said $DSC_Alarm) {
               print_log "Alarm system data = $log\n";
            }

            mh.ini entry of 'DSC_Alarm:2_serial_port=com5'

            $DSC_test = new DSC_Alarm('DSC_Alarm:2');
            if (my $state = state $DSC_test) {
               print_log "Alarm system state change, state = $state\n";
            }

          Examples of typical DSC alarm system event/log entries:

        Mon 10/09/00 17:09:00 DSC_Alarm.pm Initialized Mon 10/09/00 17:10:16
        17:10 10/09/00 System [*1] Access by User Mon 10/09/00 17:12:28
        17:12 10/09/00 System Partial Closing Mon 10/09/00 17:12:28 17:12
        10/09/00 System Bypass Zone 1 Mon 10/09/00 17:12:28 17:12 10/09/00
        System Bypass Zone 2 Mon 10/09/00 17:12:28 17:12 10/09/00 System
        Bypass Zone 4 Mon 10/09/00 17:12:29 17:12 10/09/00 System Closing by
        User Code 40 Mon 10/09/00 17:12:29 17:12 10/09/00 System Armed in
        Away Mode Mon 10/09/00 17:12:45 17:12 10/09/00 System Opening by
        User Code 2 Mon 10/09/00 17:14:42 17:14 10/09/00 System Closing by
        User Code 40 Mon 10/09/00 17:14:43 17:14 10/09/00 System Armed in
        Away Mode Mon 10/09/00 17:14:47 17:14 10/09/00 System Opening by
        User Code 40 Mon 10/09/00 17:22:28 17:22 10/09/00 System [*1] Access
        by User Mon 10/09/00 17:33:39 DSC_Alarm.pm Initialized Tue 10/10/00
        09:47:38 09:47 10/10/00 System Closing by User Code 40 Tue 10/10/00
        09:47:38 09:47 10/10/00 System Armed in Away Mode Tue 10/10/00
        17:48:42 17:48 10/10/00 System Opening by User Code 40 Tue 10/10/00
        23:37:33 23:37 10/10/00 System Closing by User Code 40 Tue 10/10/00
        23:37:33 23:37 10/10/00 System Armed in Away Mode Wed 10/11/00
        07:38:11 07:38 10/11/00 System Opening by User Code 40

  List of functions
    add_sound
        Assigns a sound file to an event name. This allows us to create and
        review event sounds. See mh/code/common/event_sounds.pl for an
        example.

          Example:

            add_sound barcode_scan => 'sound_nature/bird.wav', volume => 20 if $Reload;
            if ($state = state_now $barcode_scan) {
                play 'barcode_scan';
            }

    browser
        Calls up the web browser defined my the mh.ini browser parm

          Examples:

            browser 'http://misterhouse.net';
            browser "$config_parms{tracking_dir}/today.html" if said $v_show_tracking;

    convert_k2f
    convert_c2f
        Converts from degrees Kelvin/Centigrade to Farenheight

          Example:

            $weather{TempF} = convert_c2f($weather{TempC});

    convert_direction
        Converts 0->360 degrees into north, north east, east, etc

          Example:

            speak "Wind speed is " . round($weather{WindAvgSpeed}) .
                  " from the " . convert_wind_direction($weather{WindAvgDir});

    dbm_write =item logit_dbm
        Writes data into a dbm file. If data for that key exists, it is
        overwritten. dbm files are useful if you want to store key-value
        data or hash arrays onto disk for later use.

          Usage:

            dbm_write($log_file, $log_key, $log_data);
            logit_dbm($log_file, $log_key, $log_data);

          dbm_write simply stores data as is:    $dbm_file{$log_key}=$log_data
          logit_dbm also stores an access count: $dbm_file{$log_key}="$access_count $log_data"

          Examples:

            logit_dbm("$Pgm_Root/data/phone/callerid.dbm",
                      $cid_number, "$Time_Now $Date_Now $Year name=$cid_name");

            See display_callers for an example of how to read dbm files.

    dbm_read
        Read data from a dbm file. If a key is passed, only that one record
        is returned. Otherwise, the whole dbm is read.

          Usage:

            $value = read_dbm($dbm_file, $key);
            %data  = read_dbm($dbm_file);

    dbm_search
        Search a dbm file for matches to a string. It currently searches
        both key and value.

          Usage:

            ($count_searched, $count_matched, %results) = search_dbm($dbm_file, $search_string);

          See mh/code/bruce/phone.pl and mh/bin/display_callers for examples.

    display
        Displays the specified text string, text file contents, or bitmap
        (.gif or .jpg).

          Simple mode:
             display $file_or_text, $time, $title, $font, $window_name, $append;

          Options mode:
             display option => 'value', text => $file_or_text;

          Options:
              $time is how long (in seconds) till the box will auto-close.
                     Defaults to 120.  Use 0 to disable auto-close.
              $title is the title of the display box.
              $font  is the font (default is mh.ini parm tk_font).
              $window_name will re-use an exisiting display window of the same name.
              $append can be top or bottom.  Only meaningful with $window_name.
              width and height overrides the auto-calculated values based on text
              geomeotry places the window: +X+Y (e.g. +0+0 for upper left)
              device  Use this to pick a specific display.  For example,
                      device=alpha will display data with the &display_alpha function.
              app     This uses the parms defined in the  the mh.ini display_app parm.
                      This allows us to use custom parms with generic, common code.

          Examples:

            display "Internet message: $msg", 300, 'Internet message';
            display $f_trivia_answer;
            display '/pictures/photo.jpg';
            display text => $log, time => 0, font => 'fixed', window_name => 'log';
            display text => $text, time => 0, window_name => 'AIM', append => 'top';

            display text => $f_top10_list, time => 300, font => 'Times 25 bold',
                    geometry => '+0+0', width => 72, height => 24;

            display "device=alpha $caller";
            display "app=bingo $bingo_text";
            display app => 'bingo', text => $bingo_text;

        If -tk 1 (i.e. Tk is installed and used), the data is displayed in a
        Tk popup window. If -tk 0 or the data is echoed to the console.

        If the data is requested from a web browser, the data is echoed to
        the browser print_log window. It will be displayed to a local tk
        window only if the $time parm is specified.

    eval_with_timer
        Use this to run some code after some delay.

          Usage:

             eval_with_timer $code, $time;

          Examples:

            eval_with_timer 'print_log "hi from the past"', 60;
            eval_with_timer '$Misc{insult}{flag} = 1', 2;  # Sets flag after 2 seconds

        If you want to do this with an object state, use the set_with_timer
        method.

    file_default
        Checks to see if a file exists, and if not, returns a default file
        instead.

          Usage:

             file_default $file, $default;

          Examples:

            $f_insult = new  File_Item(&check_default_file($config_parms{speak_insult_file},
                                       "$config_parms{data_dir}/remarks/insults1.txt"));

    file_size
        Returns the size of a file, in bytes.

    file_changed (or older name of file_change)
        Returns 1 if the specified file has change since last checked.
        Returns 0 if the file has not changed. Returns undef if we don't
        know (i.e. first check after mh was started).

          Example:

            print_log "File $file has change" if file_changed($file);

    file_unchanged
        Returns 0 if the specified file has change since last checked.
        Returns 1 if the file has not changed. Returns undef if we don't
        know (i.e. first check after mh was started).

          Example:

            my $watchdog_file = '//dm/d/misterhouse/mh/data/mh.time';
            if (file_unchanged $watchdog_file) {
                speak "MisterHouse has stopped running on the Nick's box";
                set_with_timer $watchdog_light '20%', 5;
            }

    file_diff
        Returns true if the contents of the 2 specified files are different.

          Example:

            print_log "Files are different" if file_diff($file1, $file2);

    file_read
        Reads data from a file. If used in a list context, a list is
        returned, otherwise a string of all the lines.

          Examples:

             @data = file_read($file);
             $data = file_read($file);

    file_read_dir
        Returns a member => full_path hash for all members in all dirs
        passed to it.

          Examples:

            my %file_paths = &file_read_dir(@Code_Dirs);
            for my $member (keys %file_paths) {
                next unless $member =~ /(\S+).menu$/i;
                menu_parse file_read $file_paths{$member}, $1;
            }

    file_write
        Writes data into a file. Like logit, except it writes over, rather
        than appends to, a file.

          Examples:

             file_write($file, $data);

    file_cat
        Concatonates data from one file onto another. If an optional 3rd
        parm of 'top' is specified, the first files is added to the top of
        the 2nd file. If not, then it is added to the bottom.

          Examples:

            file_cat $file, $file_total;

            file_cat "$config_parms{html_dir}/aprs/week2.html",
                     "$config_parms{html_dir}/aprs/old/${Year_Month_Now}.html",
                     'top';

    file_head file_tail
        Returns the first/last few lines of a file

          Examples:

             @data = file_head($file, $lines);
             $data = file_tail($file, $lines);

           If $lines is not specified, the default is 3.
           If used in a list context, a list is returned,
           otherwise a string of all the requested lines is returned.

    filter_cr
        Returns the given string, with carriage returns and line feeds
        filtered out

          Examples:

            print_log filter_cr get "$URL/play?p=$config_parms{mp3_program_password}";

    logit
        Appends data into a log file. Use file_write to write over a file.

          Usage:

            logit($log_file, $log_data, $log_format, $head_tail);

              $log_format=0  =>  Data is written as is, no /n, no time_date stamp.

              $log_format=## =>  Every log entry is preceded with a time_date stamp.
                                 ## is passed to &time_date_stamp to determine the format.
                                 Also, /r/n is stripped so we get only one record per call.

                                 The default  $log_format is 14.

              $head_tail     =>  If 1, data is logged to the top of the file, otherwise
                                 it is added to the bottom.

          Examples:

           logit("$Pgm_Root/data/logs/wx200.$Year_Month_Now.log",  $data, 0);
           logit("$Pgm_Root/data/phone/logs/callerid.$Year_Month_Now.log", "$cid_number $cid_name");

    mht_item_add
    mht_item_delete
    mht_item_copy
    mht_item_write
        These functions can be used to manipulate mht files.

    play
        Use this to play wave or system sound files. There are 2 modes of
        calling it:

           Simple mode:
             play  $file_name

           Options mode:
             play(option => 'value', file => $file_name);

             Options are:
               address => 'ip_address' Use this to push wav files to
                                       remote computers that are enabled to received
                                       pushed wav files (e.g. Audreys).  Configure
                                       with the mh.ini parm voice_text_address_code.
                                       Can be a comma delimited list of addresses.

               rooms => 'room_names'  You need to code a &Play_pre_add_hook to enable this.
                                      See mh/code/bruce/pa_control for an example.
               volume=> dd            How loud (dd = 0->100).
               time  => $time         The amount of time to leave the PA speakers on for rooms
               mode  => $mode         $mode can be:
                 wait  : mh will pause (hang), until wav file finishes
                 loop  : mh will loop on the wav file
                 stop  : mh will stop playing the previous file
                 async : mh will play wave file asynchronously (in the background).  This is the default.

               mode   => 'unmuted'     Forces speech, even in mute and offline mode

               nolog   => 1      Does not log $file_name to the speech log.

               app    => 'xyz'   This uses the parms defined in the xyz section of
                                 the mh.ini speak_app parm.  This allows us to use custom
                                 parms with generic, common code.

               You can control the default mode parm with the mh.ini play_mode parm.

        If $file_name is a blank or comma delimited list of files, they are
        played sequentially. If it is a wild-carded file specification (e.g.
        "movement*.wav"), then a file is picked at random from the files
        that match.

        If the file does not include a path specification, the file is
        looked for in the mh/sounds directory.

        If the file starts with System or with sound_, the sound file is NOT
        logged in the speak log.

        Here are the names of some of the Windows System sounds. Use the
        Control Panel Sounds menu to change their associated wav files:

           - MenuPopup
           - SystemDefault
           - SystemAsterisk
           - SystemExclamation
           - SystemExit
           - SystemHand
           - SystemQuestion
           - SystemStart

           Examples:

                                # Play a random 'garage open/close' wav file
             if($state = state_now $garage_door) {   # $state will be open or close
               play(rooms => 'all', file => "garage_door_" . $state . "*.wav");
             }

             play(file => 'SystemAsterisk') if $Reload;

             play "fun/*.wav" if time_cron '* 9 * * 6';

             play address => '192.168.0.82,kitchen', file => '../sounds/hello_from_bruce.wav';

    print_log
    print_msg
    print_speaklog
        Use these to print to the various logs. These logs are listed in Tk
        and web frames.

        print_log is typically used for fairly frequent, not too important
        messages. If you want the message logged to a specific file (in the
        directory pointed to by mh.ini parm data_dir), you can prefix the
        text with log=my_logfile. For example:

          print_log 'log=test1.log This message is logged in test1.log';

        print_msg is typically used for less frequent, more important
        messages. This is not currently shown on the default web windows.

        print_speaklog is called by the speak and play functions, so should
        not be normally used in user code.

        If you pass multiple arguments to these functions, they will print
        them with a space between, just as the perl print function does.

    round
        Use this function to round off a number. Usage:

           round $number, $digits

        If $digits < 10, $number is rounded to $digits to the right of the
        decimal

        If $digits >= 10, $number is rounded to $digits nearest $digits

        Examples:

           $time_left = round $time_left, 1; Round to nearest tenth
           $time_left = round $time_left, 2; Round to nearest hundredth

           $rank = round $1, 100;  # Round to nearest 100
           $rank = round $1, 1000; # Round to nearest 1000

    run You can use the run function to run a program as a separate process.
        On Windows, the perl Win32::Process function is used, and on Unix,
        the program is forked with &.

        If you specify 'inline' as the first argument, the program will not
        be a background process/forked. mh will pause until the program is
        done.

        If you want to track when the process finishes, use a Process_Item
        object instead.

          Examples:
             run('IR_cmd VCR,3,6,RECORD');  # Start a VCR recording
             run 'rasdial /disconnect';     # Log off from the net (on Windows)
             run 'inline', 'some_fast_command_here';
             run 'mplayer.exe /play /close c:\win98\media\canyon.mid'; # Play a midi file

    run_after_delay
        You can use this to create events with built in delays without
        causing mh to pause

           Example:

                           # After 2 seconds, this evals the print_log string
             run_after_delay 2, "print_log 'Ending delay test 1'";

                           # This runs anonymous subs to print after 2 and 3 seconds.
             run_after_delay 2, sub {
               print_log "Printed after 2 seconds";
               run_after_delay 1, sub {
                 print_log "Printed after 1 more second seconds";
               }
             }

    run_voice_cmd
        You can use this to have one event trigger another voice command
        event.

           Example:

              run_voice_cmd 'Get the top10 list' if time_now('6:30 AM');

    SendKeys WaitForAnyWindow SetWindowText GetWindowText SetFocus
    EnumChildWindows sendkeys_find_window
        On Windows systems, use these functions to control other programs.
        Full documentation is mh/lib/site/Win32/setupsup.html.

           SendKeys($window, $keystr, $activate, [$timeout])
           WaitForAnyWindow($title, \$window, $timeout, [$refresh])
           SetFocus($window)
           EnumChildWindows($window, \@childs)

        WaitForAnyWindow will set the window handle for the window that
        matches the (sub)string specified in $title.

        WaitForWindow is like WaitForWindow, but requires an exact string
        match for the window title.

        SetWindowText is useful in changing a genericlly named window to a
        specific name that can then be accessed via WaitForAnyWindow. Also,
        since WaitForAnyWindow will match the first (left most) part of any
        title, GetWindowText allows one to get window specific info that can
        be used to determine the status of the window application.

        SendKeys sends the keys. Here is an excerpt of the valid key list:

           ALT+ alt down ALT- alt up CTRL+ ctrl down CTRL- ctrl up SHIFT+ shift down SHIFT- shift up
           TAB tabulator RET return ESC escape BACK backspace DEL delete INS insert HELP help
           LEFT arrow left RIGHT arrow right UP arrow up DN arrow down PGUP page up PGDN page down
           BEG pos1 END end F1 function 1 ... F12 function 12
           NUM0 0 on the num block ... NUM9 9 on the num block
           NUM* multiply key on the num block
           NUM+ add key on the num block NUM- minus key on the num block NUM/ divide key on the num block

        SetFocus sets the focus to $window. It does not activate the window
        (the foreground application will not be changed if $windows belongs
        to another application).

        EnumChildWindows enumerates all child windows that belong to $window
        and returns the handles in the @childsCODE> array. $window must be a
        valid window handle @childs must be an array reference.

        sendkeys_find_window is an mh subroutine that calls
        WaitForAnyWindow. If the window is not found and $program is
        specified, it will start $program and wait for the window to appear,
        then return the window handle.

           sendkey_find_window($title, [$program]);

        Here are a few examples:

                            # Start winamp, if it is not running
           unless (&WaitForAnyWindow('Winamp', \$window, 100,100)) {
               print_log "Starting winamp";
               run $config_parms{mp3_program};
           }

                            # Send/receive mail, using sendkeys_find_window to start the program
                            # if it is not already running
           if (my $window = &sendkeys_find_window('Outlook', 'D:\msOffice\Office\OUTLOOK.EXE')) {
              my $keys = '\\alt+\\tss\\alt-\\';  # For Outlook Express
              my $keys = '\\alt\\te\\ret\\';     # For Outlook
              &SendKeys($window, $keys, 1, 500);
           }

    respond
        This function can be used to send the resulting text of a command or
        query to a specified target. By default, valid targets are:

          display -> Text is passed to the display function.
          email   -> Text is passed to net_mail_send
          im      -> Text is passed to net_im_send
          log     -> Text is passed to print_log
          speak   -> Text is passed to speak
          tk      -> Text is passed to speak (or display if long)
          web     -> Text is passed to the web browser.

        All parms given to respond are passed to the target function. So,
        for example, you can specify a specific email account using the to
        parm, like this:

          respond target => 'email', to => 'joe@cops.com', text => 'Alarm just went off';

        or as with the speak and display functions, you can specify your
        parms in-line, like this:

          respond 'target=im to=joe@jabber.com pgm=jabber Alarm just went off';

        The default respond target it whatever set_by is, so for example, if
        a command is run from an im client, text sent to any respond called
        by that command will be sent only to that im client. You can also
        specify multiple targets. For example:

          respond 'target=speak,im,email app=notice  The fire alarm just went off';

        In this case, the app parm is only recognized by the speak function,
        and would be ignored by the im and email functions.

        When called with no target, the default is to speak, or display if
        the text is long.

        You can specify new targets, or override the default target reponse
        of the above default targets, by creating respond_xyz functions in
        your user code. So for example, if we had callerid code with this:

          $caller = "Call from $caller.  Call is from $caller.";
          respond("app=phone target=callerid $caller");

        You could add this function anywhere in your user code to change how
        it is spoken and to forward the call to your im client:

          sub respond_netcallerid {
              my (%parms) = @_;
              $parms{text} =~ s/ ?\.[^\.]*$/\./;  # Drop the extra 'call from'
              &net_im_send(pgm => "AOL', text => $parms{text});
              &speak("app=phone $parms{text}");
          }

    speak
        Passes specified text to the TTS program. There are 2 modes of
        calling it:

           Simple mode:
             speak 'text to speak';
             speak 'option=value text to speak';

           Options mode:
             speak(option => 'value', text => 'text to speak');

           Default options can be specified with mh.ini parms, using a
             speak_ prefix.  For example:  speak_voice = mike

             Options are:
               address => 'ip_address' Use this to push TTS synthesized wav files to
                                       remote computers that are enabled to received
                                       pushed wav files (e.g. Audreys).  Configure
                                       with the mh.ini parm voice_text_address_code.
                                       Can be a comma delimited list of addresses.

               rooms   => 'room_names' You need to code a &Speak_pre_add_hook to enable
                                       this.  See mh/code/bruce/pa_control for an example.

               mode    => 'unmuted' Forces speech, even in mute and offline mode

               nolog   => 1       Does not log the spoken text to the speech log.

               app     => 'xyz'   This uses the speak parms defined in the xyz section of
                                  the mh.ini speak_app parm.  This allows us to use custom
                                  speak parms with generic, common code.

             These options are for the Unix Festival speech engine (but don't work yet):
               rate   => '+-nn%'       (e.g. +10% or -30%)
               voice  => 'name'

             These are for Unix IBM ViaVoice TTS engine (mh.ini sound_program=vv_tts):
               default_volume xyz => default volume when -volume not set
               volume xyz         => volume setting for both play and voice unless specified
               voice_volume xyz   => voice volume setting
               voice xyz          => voice #
               nomixer            => do not use built in mixer support
               play        xyz    => play's sound file xyz

             These options are currently for the windows MS TTS engine only.
               mode   => any of the following
                stop, pause, resume, rewind, fastforward, slow, normal, fast, dd

               voice  => 'name'
                 This can be any voice listed in the mh.ini voic_names parm or
                 any of the following:
                    random => randomly selects a voice
                    next   => selects the next voice in the voice_names list
                    all    => uses a different voice for each word.  Note this was
                              inspired by dictionaraoke.org, but not all that useful.
                              Only works with xml enabled engines (MSV5 and NaturalVoice)

               volume => dd (dd = 0 -> 100).

               rate   => fast,normal,slow,dd
                         For the V4 engine, dd is words per minute.
                         For the V5 engine, dd is -10 -> 10 for slowest to fastest

               pitch  => dd (dd = -10 -> 10)

               card   => d (d = 1,2,3...) Picks which sound card to used (MSV5 only)
                         d can also be a text string, if you use the mh.ini voice_text_cards option
                         to define which cards to enable (e.g. voice_text_cards = live,audigy).
                         To output to more than one card, specify a comma delimited list.

               to_file => xyz.  Saves speech to file xyz.

           With the MS TTS V4 engine, the rate parm effects all subsequent
           spoken text (volume and voice parms do nothing).

           With the MS TTS V5 engine, the volume, rate, and voice parms will effect
           only the specified text.   If you want to change the default for all text,
           specify the parm, but no text (e.g. speak voice => 'Sam')

           The MS TTS V5 engine also supports embedded XML parms for other controls (e.g. pitch,
           silence, pronounce, emphasis).   See mh/doc/ms_speech_xml_example.* for examples.

           You can use the mh.ini voice_names parm to correlate generic voice names to
           specific names.  For example:

              voice_names = female=>Crystal, male=>Rich16, male1=>Rich16, male2=>Mike,
                            Rich=>Rich16, Mary=>Crystal, Sam=>Mike

           Using voice=none will cause no text NOT to be spoken.

           Examples:

            $test_voice1 = new Voice_Cmd "Say something with at a volume of [50,100]";
            $test_voice2 = new Voice_Cmd "Say something at a [fast,slow,normal] speed";
            $test_voice3 = new Voice_Cmd "Say something in voice [male,female]";

            speak(volume => $state,
                  text => "This is an example at a volume of $state")    if $state = said $test_voice1;
            speak(rate   => $state,
                  text => "This is an example at a rate of $state")    if $state = said $test_voice2;
            speak(voice  => $state,
                  text => "This is an example of a $state voice") if $state = said $test_voice3;

            $Save{mode} = 'mute' if time_cron '0 10-21 * * 1-5';
            speak 'mode=unmuted  The front door just opened';
            speak mode => 'pause';
            speak mode => 'resume', rate => 120;

            speak "address=bedroom,kitchen Wake up or die!";

            speak "Now I am really, <pitch absmiddle='10'/>really excited!"

            speak to_file => "$config_parms{data_dir}/test_tts.wav",
                  text => read_next $house_tagline if $New_Minute;

            speak app => 'email', text => 'You have new email';
            speak app => 'timer', text => 'The timer has expired';

            More examples can be found in mh/code/common/test_speak.pl

    time_add
        Use this to add/delete an offset to a time value, using the same
        syntax as offsets allowed for in time_now.

           Examples:

            my $fishtank_light_on_time,$fishtank_light_off_time,$fishtank_light_duration;
            if ($Startup) {
              $fishtank_light_on_time  = time_add "$Time_Sunrise_Twilight+3:00";
              $fishtank_light_off_time = time_add "$Time_Sunset_Twilight+4:00";
              $fishtank_light_duration = time_add "$fishtank_light_off_time-$fishtank_light_on_time";
            }

    time_diff
        This function returns the time difference between 2 time values. The
        roundoff time unit is dependent on how big the difference is between
        the 2 times. For example, if the time difference is > 5 days, the
        returned value will be in days.

           Examples:

           my $diff = time_diff $Moon{time_full}, $Time;
           speak "The last full moon was $diff ago";

    time_date_stamp
        You can use this function to return a time/date string.

          Usage:

            time_date_stamp($format, $file_or_time);

            $format can be any of the following:

              1:   Monday, 08/26/96  11:01 PM
              2:   Monday Jul 27 14:00 1996  (seems to be more compatable with javascript parsing)
              3:   Monday Jul 27 at 6 AM
              4:   6:05 AM on Monday, Jul 27
              5:   6:05 AM
              6:   Mon, Jul 27
              7:   Mon 01:02PM
              8:   6:05 (skip the AM PM)
              9:   04/14/97 10:28 PM
             10:   year_month, with leading 0, so log files are sorted ok (e.g. 97_01)
             11:   01/31/98 (mm/dd/yy)
             12:   04/14/97 14:28:00
             13:   14:28:33
             14:   Mon 04/14/99 14:28:00
             15:   Sunday, December 25th
             16:   04/14/97  2:28:00 PM
             17:   2001-04-09 14:05:16  (POSIX strftime format)
             18:   20011201 (i.e. YYYYMMDD)

        The second argument can be either time (in epoc seconds) or a file,
        where it will use the time that it was last modified. The default is
        $Time (current time).

            $lcd_data{1} = &time_date_stamp(14, $Time);

            speak "File was changed today" if time_date_stamp(6, $file) eq time_date_stamp(6);

        Note: The mh.ini time_format and date_format parms can be used to
        modify these formats to non-us standards (e.g. no AM/PM, use dd/mm
        instead of mm/dd).

    time_cron
        The cron time format matches the Unix cron format.

            Cron Format:
              minutes hours day_of_month month day_of_week

                minutes: 0-59
                hours:   0-23
                dom:     1-31  (day of month)
                month:   1-12
                dow:     0-6   (day of week 0=Sunday 6=Saturday)

              You can use a comma delimited list for any of these fields.  * matches all values.

        By default time_cron only operates on the pass when a new minute
        starts (i.e. $Second == 0). This can be overridden using a 2nd
        'second' parameter to specify which second to trigger on. If this is
        set to '*', it returns true for all passes for the minutes that it
        matches on. This may be useful if a movement sensor is to trigger
        different events at different times.

           Examples:

                         # Speak time every 15 minutes, between 7 am and 8:45 am on weekdays only
            speak $Time_Now  if time_cron '0,15,30,45 7,8 * * 1-5';

                         # Change PA speaker mode 30 seconds past at 9 pm, every day
            pa_sleep_mode('kids', 1) if time_cron('* 21 * * * ', 30);

            if (state_now $movement_sensor eq ON and !$Save{woken_up} and
                time_cron('* 6-9 * * 1', '*') )  {
               speak "It is Monday morning. Remember to put the rubbish out.";
               $Save{woken_up} = 1;
            }

    time_greater_than
    time_greater_or_equal
    time_less_than
    time_less_or_equal
        These return true if the specified time is greater, less than, or
        equal to the current time. Format can have the same sorts of offsets
        as described in time_now below. Note unlike time_now which is only
        true once, these functions will return a true value for every pass
        that they are true.

           Example:

              curtain_on('bedroom', OPEN) if
                            time_cron('22 6 * * 1-5') and
                            time_greater_than("$Time_Sunrise + 0:15");

    time_between
        Returns true if the current time is between the 2 specified times.

        If the end time happens to be earlier than the start_time, then it
        tests if the start-time is more than 12 hours in the future. If so,
        it subtracts 24 hours from the start-time, otherwise it adds 24
        hours to the end-time.

           Example:

               speak 'Who goes there' if
                    state_now $motion_sensor and
                    time_between '10 pm', '6 am';

    time_now
        time_now is evaluate to true for the 1 pass that matches the
        specified date time. Date is optional. You can specify + or -
        offset, in hours:minutes:seconds. Time can include AM/PM or be in 24
        hour format.

        A optional 2nd 'second' parameter can be used to specify which
        $Second it returns true on (default is on a new minute, $Second ==
        0). If this is set to '*', it returns true for all passes for the
        minute that it matches on.

        The best way to see how it works is to look the following examples:

           Examples:

             run_voice_cmd 'close the living room curtains'  if time_now $Time_Sunset;
             set $backyard_light ON if time_now("$Time_Sunset + 0:15");
             set $left_bedroom_light +50 if $Weekday and time_now "$wakeup_time - 0:01";
             speak "Remember dentist appointment" if time_now '5/27/98 8:15 AM';
             run('min', 'IR_cmd VCR,4,RECORD') if time_now('2/07 17:59', 45);
             run('min', 'IR_cmd VCR,STOP') if time_now "$date $stop - 00:01";

    time_random
        Use time_random to code random events. It uses the same time format
        as time_cron, but includes frequency parameter that specifies how
        often the event should trigger. Frequency is the average number of
        minutes between occurrences.

          Usage:
             time_random($cron_spec, $frequency)

          Examples:

                      # Speak something goofy once an hour on weekends
            speak(read_next $april_fools) if time_random('* 8-22 * * 0,6', 60);

                      # Toggle a light on an off randomly every 30 minutes
            if (time_random('* 18-22 * * *', 30)) {
              $state = (ON eq state $bedroom_light) ? OFF : ON;
              set $bedroom_light $state;
            }

            print 'test value=2'  if time_random '* * * * *',  2; # Fires every other minute;
            print 'test value=10' if time_random '* * * * *', 10; # Fires every other 10th minute;
            print 'test value=1'  if time_random '* * * * *',  1; # Fires every minute (not useful)

    time_random_offset
        Use time_random_offset to code a random time around a time_now
        formated time. It takes two arguments. The first is the same as the
        time_now function, '6:45 PM' for example. The second is an offset in
        minutes (60), seconds (:45), or minutes and seconds (2:30). The
        function returns true at some random time between the time specified
        and the offset.

        The offset can only be positive and if you restart between the time
        specified and the random offset, it won't fire.

          Usage:
             time_random_offset($time_now_spec, $offset)

          Examples:

            speak "random test" if &time_random_offset('1:20 pm', '1:23');

            set $light ON if &time_random_offset(&time_add("$Time_Sunset + :15"), 15));

  List of tk widget functions
    tk_button tk_mbutton
        Use these functions to add a Tk button widget to the mh tk grid
        (tk_button) or the tk menu_bar (tk_mbutton). &tk_button will accept
        multiple variables, displaying them in a row in the grid.

           Usage:
            &tk_mbutton('Button Name', \&subroutine);
            &tk_button('Button1', \&sub1);
            &tk_button('Button1', \&sub1, 'Button2', \&sub2,'Button3', \&sub3);

           Examples:

            &tk_mbutton('Help', \&help);
            &tk_button('Reload(F1)', \&read_code, 'Pause (F2)', \&pause,
                       ' Exit (F3) ', \&sig_handler, 'Debug(F4)',  \&toggle_debug,
                       'Log(F5)', \&toggle_log);

    tk_checkbutton
        tk_checkbutton adds a Tk checkbutton widget to the mh tk grid. It
        will accept multiple variables, displaying them in a row in the
        grid.

           Usage:
            &tk_checkbutton('text',  \&var1);
            &tk_checkbutton('test1', \&var1, 'text22', \&var22, 'text3', \&var33);

           Examples:

            &tk_checkbutton('Debug on', \$config_parms{debug});
            &tk_checkbutton('Sleeping Parents', \$Save{sleeping_parents},
                            'Sleeping Kids', \$Save{sleeping_kids});

    tk_entry
        Use this function to allow for arbitrary data to be entered via the
        mh tk grid.

           Usage:
            &tk_entry('Entry label:', $state_based_object);
            &tk_entry('Entry label:', \$variable);
            &tk_entry('Entry label:', \$variable, 'Entry label2:, \$variable2);

           Example:
            &tk_entry('Sleep time:', \$Loop_Sleep_Time);
            &tk_entry('Test in 1', \$Save{test_input1}, 'Test in 2', \$Save{test_input2});

        Note: The $variable reflects the data, as it is being entered. If
        you want to test on the data only after the RETURN key has been hit,
        use %Tk_results array. The $variable is copied to $Tk_results{'Entry
        label:'} only after the RETURN key has been entered.

        Now you can now also use a state based object (like Generic_Item) to
        store/monitor/change the tk_entry text.

           Examples:

            &tk_entry('TV search', \$Save{tv_search});
            if ($state = $Tk_results{'TV search'}) {
               run qq[get_tv_info -times all -keys "$state"];
               set_watch $f_tv_file;
               undef $Tk_results{'TV search'};
            }

           $mp3_search_text =  new Generic_Item;
           $mp3_search_text -> tie_event('print_log "mp3 search text is now $state"');
           &tk_entry('mp3 Search', $mp3_search_text);

    tk_label tk_mlabel
        Use these functions to add a Tk label widget to the mh tk grid
        (tk_label) or the tk menu_bar (tk_mlabel). To avoid duplicate labels
        after a reload, pass a label name as a 2nd parm.

           Usage:

             &tk_mlabel(\$variable, $label);

           Example:

             &tk_mlabel(\$Save{email_flag}, 'email flag');

    tk_radiobutton
        Use this function to create radio buttons in the mh tk grid. If
        labels are not specified, the values are displayed.

           Usage:
            &tk_radiobutton('Button label:', $state_based_object, ['value1', 'value2', 'value3']);
            &tk_radiobutton('Button label:', \$variable, ['value1', 'value2', 'value3']);
            &tk_radiobutton('Button label:', \$variable, ['value1', 'value2', 'value3'],
                                                         ['label1', 'label2', 'label3']);

           Examples:

            &tk_radiobutton('Mode',  \$Save{mode}, ['normal', 'mute', 'offline']);
            &tk_radiobutton('Debug', \$config_parms{debug}, [1, 0], ['On', 'Off']);
            &tk_radiobutton('Tracking', \$config_parms{tracking_speakflag}, [0,1,2,3],
                            ['None', 'GPS', 'WX', 'All']);

            my $alarm_states = "Disarmed,Disarming,Arming,Armed,Violated,Exit Delay,Entry Delay";
            my @alarm_states = split ',', $alarm_states;
            $alarm_status    = new Generic_Item;
            &tk_radiobutton('Security Status', $alarm_status, [@alarm_states]);

            $v_alarm_status  = new Voice_Cmd "Set the alarm to [$alarm_states]";
            $v_alarm_status -> tie_items($alarm_status);

            print_log "Alarm status changed to $state" if $state = state_now $alarm_status;

    trigger_set
    trigger_get
        You can create triggers to easily run mh code on specified events.
        When you create a trigger with trigger_set, mh will create or modify
        the code_dir/triggers.mhp file.

        Use the web interface at http://localhost:8080/bin/triggers.pl (also
        under the ia5 MrHouse Home button) to view and update triggers.

        Trigger functions are:

          trigger_set($trigger, $code, $type, $name, $replace)

            Creates or modified an existing trigger.  Only $trigger and $code are
            required.  $type defaults to OneShot (runs only once) and $name
            will default to an unique auto-generated name.  If $name is specified
            and already exists, $name will be incremented, unless $replace=1.

            Example:

              &trigger_set("time_now '$date $time - 00:02'",
                           "speak 'Something cool happens in 2 minutes'");

              &trigger_set("time_now '$Save{wakeup_time}'",
                           "speak 'Time to wake up'", "NoExpire", "Wakeup Trigger", 1);

              Another example of using triggers is in mh/code/common/tv_grid.pl

          trigger_get($name}
            Returns ($trigger, $code, $type, $triggered).
            $triggered is the epoc seconds when the trigger was
            last tirggered, 0 if it has not been triggered.

          trigger_list
            Returns a list of trigger names.

          trigger_delete($name}
          trigger_copy($name}
            Delete or copies the specified trigger.

          trigger_active($name)
          trigger_expired($name)
            Returns true if the trigger is active or expired.

        Here are the valid trigger types:

          OneShot  => The trigger will run once, then changed type to Expired

          Expired  => Will be pruned from the triggers.mhp file after one week
                      and archived in data_dir/triggers.expired.

          NoExpire => Runs on every event and never expires.

          Disabled => Will stay in your triggers.mph file, but will not run.

    See mh/code/examples/tk_examples.pl for more tk_* examples.

  List of Internet functions
    get This function will retrieve data from the web (http web pages or ftp
        files). If the web page is large or the site is slow, you may want
        to use a Process_Item call to the get_url program, so the request
        can be done as a separate process and mh will not get hung up while
        waiting.

          Examples:

            my $html = get 'http://marketing.cbs.com/lateshow/topten';

            Other examples are in mh/code/bruce/internet_data.pl

    get_ip_addresses
        Returns the numeric IP address of the specified hostname. If
        hostname is blank, localhost is used. If used in a list context, all
        associated IP address are returned (e.g. dial up address and local
        address).

          Examples:

            print_log "Current IP address " . get_ip_address;

                               #  Echo dynamic IP address to the Tk gui
            if ($New_Minute and net_connect_check) {
                 $Tk_objects{ip_address} = "IP address: " . get_ip_address;
            }
            tk_label(\$Tk_objects{ip_address});

    html_unescape
        Un-escapes "%xx" data back into the original characters. Use on HTML
        FORM data.

    new_second
    new_minute
    new_hour
        These functions are like the $New_Second/Minute/Hour variables,
        except they can return true only on the nth second/minute/hour.

          Examples:

            print_log "This occurs every 10th second" if new_second 10;
            print_log "This occurs once every even hour" if new_second 2;

        If you pass no argument, it defaults to 1 (i.e. the same as the
        $New_* variables).

    net_domain_name 'address'
    net_domain_name_start 'search_name', 'address'
    net_domain_name_done 'search_name'
        These functions will return the domain_name of the last client to
        access the specified port. If the mh.ini DNS_server parm is NOT set,
        it will return the IP address instead. You can also pass it an IP
        address, instead of a server port name.

        If used in an array context, it returns the full domain name, and a
        short version of the domain name (e.g. 'www.ibm.com' and 'ibm').

        If you call net_domain_name_start, then check for
        net_domain_name_done, it will query the DNS servers as a background
        task, so mh will not pause if the DNS server response takes a while.
        The 'search_name' string must be unique for that part of the code,
        so that the net_domain_name_done test gets the correct results.

        The data is cached. If the domain has already been searched,
        net_domain_name_start will return the same results as
        net_domain_name_done.

          Examples:

                                   # Run in the foreground
           my $domain_name = net_domain_name 'http';
           my ($name, $name_short) = net_domain_name 'server_speak';
           my ($name, $name_short) = net_domain_name '204.146.18.33';
        #  Results: $name = 'www.ibm.com', $name_short = 'ibm';

                                   # Run in the background
          $v_test_dns2  = new Voice_Cmd 'Run the dns test2';
          $v_test_dns2 -> tie_event("net_domain_name_start 'test', '204.146.18.33'");
          print_log "Domain=$state" if $state = net_domain_name 'test';

    net_ping
        Checks to see if an IP address is available. Returns true if
        pingable.

          Example:

            &net_ping($host);           #  Default protocol is specified in mh.ini
            &net_ping($host, $protocol);

        Note this may cause my to hang for a while if the target is not
        pingable. A better way of testing ping-ability to use a Process_Item
        call. An example is in code/common/internet_connect_check.pl

    net_ftp
        Used to ftp data to or from an ftp server. There is also a stand
        alone version of this command in mh/bin/net_ftp, so you can run this
        command with a run or a Process_Item to avoid hanging mh with long
        running ftp sessions.

          Usage:

            net_ftp(option1 => $value1, option2 => value2 ...);

               These are the possible options:

                    server          Default is mh.ini parm net_www_server
                    user            Default is mh.ini parm net_www_user
                    password        Default is mh.ini parm net_www_password
                    dir             Default is mh.ini parm net_www_dir
                    file            Name local/remote file to get/put
                    file_remote     Name of remote file (if different from local file)
                    command         get/put/delete.
                    type            ASCII/binary  (default is ASCII)
                    passive         Set to 1 to get a passive FTP (sometimes needed for firewalls)

           Example:

             net_ftp(file => 'index.html', command => 'put', passive => 1);

             my $rc = net_ftp(
                file => 'c:/junk1.txt', file_remote => 'incoming/junk1.txt',
                command => 'put', server => 'misterhouse.net',
                user => 'anonymous', password => 'bruce@misterhouse.net');
             print_log "net_ftp put results: $rc";

            $v_test_ftp = new Voice_Cmd 'Test background ftp [get,put]';
            $p_test_ftp = new Process_Item;
            if ($state = said $v_test_ftp) {
              set $p_test_ftp
                  "net_ftp -file c:\junk1.txt -file_remote incoming/junk1.txt " .
                  "-command $state -server misterhouse.net " .
                  "-user anonymous -password bruce\@misterhouse.net";
              start $p_test_ftp;
            }
            print_log "Ftp command done" if done_now $p_test_ftp;

    net_connect_check
        Returns true if connected to the Internet. If the mh.ini parm
        net_connect=persistent, this always returns true.

    net_socket_check
        Returns true if the specified host:port is avilable, 0 otherwise.

          Usage:

            net_socket_check($host_port, $protocol);

          Examples:

            return &net_socket_check("$host:$port");

    net_im_send
        Used to send and AOL Instant Message, Jabber, or MSN message across
        the internet.

        Information on AIM clients (available for various different
        platforms) can be found at
        http://www.aol.com/aim/faq/getstarted.html . Tik, a tk based client
        for use on a Unix os, can be found at http://tarp.linuxos.org/tik .
        You can register for an AIM name at
        http://www.aol.com/aim/faq/registration.html .

        Information on Jabber clients (many open sourced clients are
        available for different platforms AND the protocol is open) can be
        found at http://www.jabber.com and http://www.jabbercentral.org.

          Usage:
            net_im_send(option => value);

             These are the options:

                pgm         Default aol.  Can also be jabber or msn
                password    Default is mh.ini parm net_jabber_password  or net_aim_password
                from        Default is mh.ini parm net_jabber_name      or net_aim_name
                to          Default is mh.ini parm net_jabber_name_send or net_aim_name_send
                server      Default is mh.ini parm net_jabber_server (e.g. jaber.com)
                resource    Default is mh.ini parm net_jabber_resource (this can be left blank)

                text        Message
                file        Message.  You can use the text and/or file options.

        The first time you send a message, mh will pause for a few seconds
        while it logs onto a server. Subsequent messages use the same
        signon, so are sent much faster.

        Once you have been logged in, mh will also display incoming
        messages. This code has lots of other possibilites that can be
        added, since jabber is XML based and very flexable.

          Example:

            net_im_send(pgm => 'jabber', text => "Stock summary\n  $Save{stock_data1}\n  $Save{stock_data2}")

            net_im_send(text => "Internet mail summary for $Date_Now $Time_Now",
                        file => "$config_parms{data_dir}/get_email2.txt") if time_cron '05 12 * * 1-5';

        More examples are in mh/code/common/internet_im.pl

    net_mail_send
        Used to send email. To run commands via email, see FAQ question
        2.12: Can do I send mh comands via email?

        There is also a stand alone bin/send_email command you can use if
        you find &net_mail_send causes mh to pause.

          Usage:
            net_mail_send(option => value);

             These are the options:

                server      Default is mh.ini parm net_mail_ACCOUNT_server or
                                              parm net_mail_ACCOUNT_server_send
                port        Default is 25 or  parm net_mail_ACCOUNT_server_send_port
                from        Default is mh.ini parm net_mail_ACCOUNT_address
                to          Default is mh.ini parm net_mail_ACCOUNT_address
                            This can be a comma or semicolon delimited list of addresses

                account     This is the ACCOUNT field used in finding the above parms.
                            It defaults to the mh.ini parm net_mail_send_account.

                subject     Default is 'Email from Mister House'
                text        Body of the message

                file        File with the body of the message or a file whose
                            contents you want attached to the note.
                            Note: You can not use file and text at the same time, as they will be combined.

                filename    Name to give file attachement.  Defaults to file parm.

                mime        Set to the mime type (used if a file is attached).
                            Current recognized types are txt,pl,zip,bin,exe,jpg,gif,png, and html.
                            If not specified, the extention of the file parm is used.
                            Use bin for an arbitary binary file.

                baseref     If sending html with mime => 'html', use this to set the BASE HREF

                priority    Can be 1->5 (1 is high, 5 is low).  Default is 3.

          Example:

            net_mail_send(text => "Test email sent at $Time_Now\n\n");

            net_mail_send(account => 'Bruce', to => 'winter@misterhouse.net', text => $msg);

            net_mail_send to => 'winter@misterhouse.net,santa@claus.net', text => 'hiho';

            net_mail_send(subject => 'test an html file attachment',
                          baseref => 'localhost:8080',
                          file    => '../web/mh4/widgets.html', mime  => 'html');

            net_mail_send(subject => 'test a gif file attachement',
                          file    => '../web/graphics/goofy.gif');

                                # Use run to launch a background process
            run 'send_mail -subject "test" -text "Test background send_mail"';

    net_mail_count
        Returns the number of email message on the specified account.

          Usage:
            net_mail_count(option => value);

             These are the options:

                server      Default is mh.ini parm net_mail_ACCOUNT_server
                port        Default is 110 or parm net_mail_ACCOUNT_server_port
                user        Default is mh.ini parm net_mail_ACCOUNT_user
                password    Default is mh.ini parm net_mail_ACCOUNT_password

                account     This is the ACCOUNT field used in finding the above parms.
                            It defaults to the mh.ini parm net_mail_send_account.

          Example:

            my $count = net_mail_count(account => 'Bruce');
            speak "Email account Bruce has $count new email messages";

    net_mail_summary
        This returns a pointer to a hash array containing info on mail for a
        specified email account. Check out the mh/bin/get_email program for
        a usage example. Rather than call this with mh code directly,
        calling get_email with a Process_Item, so mh does not pause while
        email is being checked. See mh/code/common/internet_mail.pl for an
        example.

    net_mail_read
        Returns a list array of pointers to data read from an email account.
        This is not tested and needs to be documented.

    net_mail_delete
        Deletes mail from an account. A dangerous, but requested, function.

  List of companion programs
    These programs are included in mh mh/bin directory. Most of these are
    meant to be called from mh events, as separate processes, but they can
    also be run stand alone. You can get the help text for most of these
    programs with the -h option (e.g. get_tv_grid -h).

    alpha_page
          alpha_page sends alphanumeric page.

          Usage:

            alpha_page [options]

              -h              => This help text
              -help           => This help text
              -name xyz       => name of recipient (must be defined in mh.ini)
              -pin xyz        => pin number
              -message xyz    => text of message to send

          Example:
            alpha_page -pin 123456 -message 'Bring home bread and milk'
            alpha_page -name craig -message 'Bring home bread and milk'

    backup_data
        backup_data walks directories and stores selected files to gziped
        tar files.

          Usage:

           backup_data [options] dir1 dir2 etc

            -h        => This help text

            -file xyz => Name of tar file.  Default is backup
            -size xyz => Do NOT store files > xyz KBytes.  Default is 100
            -skip xyz => Skip any file or dir that matches regex xyz
            -age  xyz => Only back up files changed in the last xyz days

            -no_zip   => Do NOT gzip the tar file.
            -no_zip   => Do NOT suffix the files with date stamp.

            -int      => Use the internal perl tar and gzip modules, rather
                         then external tar and gzip programs.  This is slower
                         and users more memory (> the data being tared).

          Examples:
            backup_data /www
            backup_data -size 10 -no_zip -int /usr/local/bin
            backup_data -file /backup/misc /bin //misterhouse.net/bin
            backup_data -file /backup/mh -skip "(/tv$)|(/articles$)" /misterhouse
            backup_data -file /backup/mh_articles  -size 100000 /misterhouse/articles
            backup_data -file /backup/docs -age 30 -size 100000 /docs

    display
        Use this to display a string of text or the contents of a file using
        a Tk window.

          Usage:
             display -options  text_or_file

          Options:
            -time  xyz to have the window auto-close in xyz seconds.  Set to 0 for no auto-close.
            -font  xyz to pick the font
            -title xyz to set the window title

          Examples:
            display weather_forcast.txt
            display -title 'triva answer' c:/data/triva_answer.txt
            display "Remember to take out the garbage" -time 0

    display_callers
        Use this to display a Tk window with a list of the incoming and
        outgoing phone logs. An example of how to create these logs is in
        mh/code/bruce/phone.pl

          Usage:
            display_callers (no argument -> uses latest logs)
            display_callers 1997_11  (Look only at November, 1997 logs)

    find_files
        find_files finds files on shared networked Windows and samba
        directories

          Usage:
           find_files [options] search_string

            -h or -help  => This help text
            -v           => Verbose (shows dirs as they are searched)

            -boxes x,y,z => Search for files on boxes x,y, and z.  Defaults to all.
            -dirs  x,y,z => Search only dirs x,y, and z.  Defaults to all.
            -skip  x,y,z => Ignore files/dirs with strings x,y, and z.

          Examples:
            find_files mp3
            find_files -v -boxes "house,dm,c2" mp3
            find_files -dirs "//house/c,//dm/d" mp3

    find_programs
        find_programs finds programs running on networked Windows computers.

        Requires WMI to be installed on both/all computers.

          Usage:
           find_programs [options] box_list [search_string]

            box_list is a comma delimited list of boxes to search
            search_string lists only programs that match

           options:
            -h or -help  => This help text
            -all         => Show/search all programs

          Examples:
            find_programs house
            find_programs "house,dm,c2" quake
            find_programs -all "house,dm,c2"

    get_email
        Use this program to check all the email accounts defined in your
        mh.ini file. It will create the following files:

          mh/data/get_email.data
             It uses this database so see what email is new
             since it was last called.   To get a complete
             list of all email (not just new/recent mail).
             delete this file before running get_email

          mh/data/get_mail.txt
             This summarizes how much mail there is and from who.
             Here is an example of what the file might look like:

               Email account bruce has 2 new email messages from John Doe and Bill Gates

          mh/data/get_mail.flag
             This has a string of digits, one for each account,
             that shows how much mail is each account.

          mh/data/email
             This directory will have one weeks worth of html mail files,
             by account and day (e.g. winter_tue.html), as well as a
             latest.html file that just shows the unreceived mail.

        You can create a get_email_rule.pl file to control how this program
        summarizes mail. See get_email_rule_example.pl for an example.

        Use the mh.ini net_mail_scan_age parm to set the age, in minutes, of
        email to report/scan. If blank, (default), get_email reports on all
        mail on your server. If your have your mail client set to 'leave
        mail on server' (useful if you want to use appliances like Audrey to
        also read mail), you will probably want to set this to something
        like 15.

        Here is an example of how to call it from mh (see
        mh/code/common/internet_mail.pl for the complete example):

          $p_get_email = new Process_Item('get_email');
          start $p_get_email if !$Save{sleeping_parents} and
                                $New_Minute and !($Minute % 20) and &net_connect_check;

          if (done_now $p_get_email) {
              my $email_text = file_read "$Pgm_Root/data/get_email.txt";
              speak "rooms=all $email_text" if $email_text;
              $Save{email_flag} = file_read "$Pgm_Root/data/get_email.flag";
          }
          &tk_mlabel(\$Save{email_flag});

    get_mp3_data
        get_mp3_data reads mp3 directories and stores the results in a dbm
        file. This data is used by mh/bruce/mp3_playlist/pl to search/play
        mp3 tag data, file names, and playlist files.

          Usage:

           get_mp3_data [options] dir1 dir2 etc

            -h        => This help text
            -help     => This help text

            -dbm  xyz => Stores the data in dbm file xyz.

          Examples:
            get_mp3_data c:\mp3 d:\mp3
            get_mp3_data -dbm e:\mh\data\mp3_dbm c:\mp3

    get_tv_grid
        get_tv_grid gets a TV grid/schedule from the web (clicktv.com) and
        changes so it to be used by the MisterHouse program to create VCR
        and TV event reminders. Creates a DBM for use by get_tv_info.

          Usage:
           get_tv_grid [options]
            -h        => This help text
            -help     => This help text
            -userid xyz   => xyz is your clicktv userid.  Go to http://clicktv.com  to
                             create one, or to find the generic one that matches your
                             local tv schedule.
            -day xyz      => xyz is the day  to get/filter.  Default is today.
            -hour xyz     => xyz is the hour to get/filter.  Default is 6pm.  Can also
                             be 'all' to get all hours.
            -days xyz     => xyz is the number of days to get/filter, starting
                             with -day.
            -channels xyz => xyz is the number of channels to filter.  Default is 999.
            -infile  xyz  => xyz is  original input file.   Default is
                             web/tv/clicktv/day_hour.html.  If this file is missing
                             or old, a new file will be retrieved from the web.
            -outfile xyz  => xyz the filtered output file.
                             Default is -outdir/day_hour.html
            -outdir  xyz  => xyz the directory the outfiles will be put in.
                             Default is mh.ini parm html_dir/tv
            -label xyz    => Use xyz as the link lable.  Default is "VCR".
            -reget        => Re-read  the clicktv web page, even if a recent file it
                             already exists.
            -redo         => Re-write -outfile xyz, even if it already exists.
            -keep_old     => Do NOT delete data from the DBM that is one month older
                             than todays date
            -debug        => turn on debug info

            -mail_to      xyz => Will email the charts to xyz
            -mail_server  xyz => xyz is the SMTP host.  Default is localhost
            -mail_baseref xyz => xyz is the http address of your mh server.  Needed if
                                 you want to control mh from the emailed web page
          Example:
            get_tv_grid -day 25 -hour 4pm -outfile my_tv.html
            get_tv_grid -days 7 -hour all
            get_tv_grid -email bruce@misterhouse.net -mail_baseref misterhouse.net:8090

          Usage:
            get_tv_grid [options]

          Options:
            -h        => This help text
            -help     => This help text

            -zip xyz      => xyz is your zip code
            -provider xyz => xyz is your TV provider ID.  See note below

            -day xyz      => xyz is the day  to get/filter.  Default is today.
            -hour xyz     => xyz is the hour to get/filter.  Default is 6pm.
                             Can also be 'all' to get all hours.

            -days xyz     => xyz is the number of days to get/filter, starting with -day.

            -channels xyz => xyz is the number of channels to filter.  Default is 999.

            -infile  xyz  => xyz is  original input file.   Default is web/tv/clicktv/day_hour.html
                             If this file is missing or old, a new file will be retreived from
                             the web.

            -outfile xyz  => xyz the filtered output file.
                             Default is -outdir/day_hour.html

            -outdir  xyz  => xyz the directory the outfiles will be put in.
                             Default is mh.ini parm html_dir/tv

            -label xyz    => Use xyz as the link lable.  Default is "Set the Vcr"

            -reget        => Re-read  the clicktv web page, even if a recent file it already exists.
            -redo         => Re-write -outfile xyz, even if it already exists.

            -debug        => turn on debug info

            -mail_to      xyz => Will email the charts to xyz
            -mail_server  xyz => xyz is the SMTP host.  Default is localhost
            -mail_baseref xyz => xyz is the http address of your mh server.  Needed if you want to
                                 control mh from the emailed web page

          Examples:
            get_tv_grid -day 25 -hour 4pm -outfile my_tv.html
            get_tv_grid -days 7 -hour all
            get_tv_grid -email bruce\@misterhouse.net -mail_baseref misterhouse.net:8090

          Note on finding your provider ID:
           Enter your zip code at http://tvlistings2.zap2it.com/
           View the html source and pick the number from value='nnnnnn'
           by doing a string search for you provider.  For example:
              <OPTION value="255248">Charter Communications - Rochester</OPTION>

    get_tv_info
        get_tv_info returns info about tv programs that match the requested
        parms. It uses a database created by the get_tv_grid program. See
        mh/code/bruce/tv_info.pl for examples on how to use this from mh.

          Version: 1.14

          Usage:

           get_tv_info [options]

            -h        => This help text
            -help     => This help text

            -channels  xyz => Will return info only for channel numbers xyz.
                              Default is all the channels found by get_tv_grid.
            -dates     xyz => Will return info only for dates xyz.
                              Default is today.  Format: month/day (e.g. 4/22).
            -times     xyz => Will return info only for shows that start at xyz.
                              Default is '6pm-10pm'.  Use 'all' for all hours.
                              Valid formats: 1 pm, 1PM, 13, 13:00.
            -early_am  xyz => Adds info for shows after midnight.
                              Formats are same as for to times.
            -increment xyz => Time increment is xyz minutes (5 or 30). Default is 5.
            -lengths   xyz => Will return info only for shows that are xyz hours long.
                              Default is any length.
            -keys      xyz => Will return info only for shows that have keywords
                              in the xyz list in their title or description.
                              Note: xyz can be a regular expression (e.g. -keys "^ER$")
            -keyfile   xyz => List of keys to search for, from a file in the data directory

              All of the above parms support these formats:
                 : comma-separated values (e.g. -dates 7/4,7/5,7/6)
                 : - delimited ranges     (e.g. -dates 7/4-7/6)
                 : + adder spec           (e.g. -dates 7/4+2)
                   Starting spec is optional (e.g. -dates +2)

            -debug        => turn on debug info
            -quiet        => turn off normal errata

          Examples:
            get_tv_info -channels "4-12" -lengths 2
            get_tv_info -channels "4,6,12" -times "7 pm"
            get_tv_info -dates "7/4-7/11" -keys "star trek, er ,dilbert"
            get_tv_info -dates +14 -keys computer
            get_tv_info -time "17-23"

          Shows which have already started are excluded unless you search
          without specifying -times or you search for -times 'all'.

    get_url
        get_url gets a web page and echoes it to STDOUT or a local file.

          Usage:
            get_url URL [local_file]

            If local_file is specified, data is stored there,
            otherwise it is echoed to STDOUT.

          Examples:
            get_url http://marketing.cbs.com/lateshow/topten/ $f_top10_html;

    get_weather
        get_weather gets weather info NOAA web servers.

          Usage:
            get_weather [options]

          Options:
            -h         => This help text
            -help      => This help text

            -city     xyz => xyz is the City  you want.
            -state    xyz => xyz is the State you want.

            -data     xyz => xyz is either conditions, forecast, or all.  Default is all.

            -refresh  xyz => xyz is the number of minutes old the cached data can
                             before it will be refreshed from the net.
                             The default is 60 minutes.

            -no_log       => Unless this option is used, the results also get filed
                             into the mh/data/web directory

          Example:
            get_weather -city Rochester -state MN

        Here is an example mh event for calling get_weather as a background
        process using the 'run' function:

          $v_get_internet_weather_data  = new  Voice_Cmd('Get internet weather data');
          $v_show_internet_weather_data = new  Voice_Cmd('Show internet weather data');
          if (said  $v_get_internet_weather_data) {
             run "get_weather -city $config_parms{city} -state $config_parms{state}";
             set_watch $f_weather_forecast;
          }
          if (said  $v_show_internet_weather_data or changed $f_weather_forecast) {
             print_log "Weather data displayed";
             display name $f_weather_forecast;
             display name $f_weather_conditions;
          }

    house
        This is a simple shell that echoes whatever you type into the
        'xcmd_file'. The name of the 'xcmd_file' is specified in mh.ini. If
        mh detects this file, it will read and execute its contents, then
        delete it.

          Examples:
            house speak hi there
            house display c:\autoexec.bat
            house Turn the backyard light on
            house XA1AJ

        The last example will fire trigger an X10 command, but only if you
        have an object defined that matches that data (e.g. X10 A1 in the
        above example).

    ical_load
        ical_load reads an icalendar calendar file from the Unix ical
        program. Optionally creates a mh code file to implement calendar
        events.

        See outlook_read for a Windows solution.

          Version: 1.0

          Usage:
            ical_load [options]

          Options:
            -help    -> help text

            -calendar_file xyz-> Location of .calendar file. It defaults to $HOME/.calendar.
            -quiet            -> do not echo data to STDOUT
            -debug            -> print out debug

            -pl_file xyz      -> Write out a mh perl code file.  These are the various
                                 formats of Calendar subjects:

                                 vcr channel_num show_name (e.g. VCR 8 Dilbert)
                                 voice_command  (e.g. Christmas lights on)
                                 message_to_speak (e.g. Today is national geek day)

                                 Note:  If the text is not a vcr or voice_command, it
                                        will be treated as a messages.

            -date mm/dd/yy    -> Get data with a start_time on date.  Default is today.
            -days ###         -> Look out ### days from -date.  Default is none, today only.

          Examples:
            ical_load -help
            ical_load -calendar_file /home/dbl/.calendar -pl_file /projects/mhcode/calendar_events.pl
            ical_load -calendar_file /home/dbl/.calendar -date 11/07/00 -days 2
            ical_load -calendar_file /home/dbl/mh/data/calendar
            ical_load

    mhl A Unix script you can use mhl to start mh (rather than calling mh
        directly) to help ensure that mh will always be running. This is a
        simple shell will restart mh if it detects that mh exited
        abnormally. You can pass in the same startup parms to mhl as you us
        for mh.

        On windows, this function is built into the mh.bat script.

    net_ftp
        net_ftp sends, receives, or deletes a file to remote site via ftp.
        There is also an identical net_ftp function, if you want to call it
        directly from mh.

          Usage:
            net_ftp -option value -option value ...

            Where -option can be any of the following
               server          Default is mh.ini parm net_www_server
               user            Default is mh.ini parm net_www_user
               password        Default is mh.ini parm net_www_password
               dir             Default is mh.ini parm net_www_dir

               file            Name local/remote file to get/put
               file_remote     Name of remote file (if different from file)
               command         get/put/delete.
               type            ASCII/binary  (default is ASCII)
               passive         0/1.  Default is 0
               timeout         Defaults to 20 (seconds)

          Example:
            net_ftp -command put -file junk1.txt -file_remote /tmp/junk1.txt

    outlook_read
        outlook_read reads MS Outlook (a windows mail/calendar program)
        folder data and optionally creates a mh code file to implement
        calendar events.

        See ical_load for a Unix solution.

          Usage:
            outlook_read [options]

          Options:
            -help    -> help text

            -version xyz -> Version of Outlook.  Use 98 if you have Outlook 98.

            -quiet       -> do not echo data to STDOUT
            -debug       -> print out debug

            -folder xyz  -> Get data from folder xyz.  It can be one of the following:
                              Deleted, Outbox, SentMail, Inbox, Calendar (default),
                              Contacts, Journal, Notes, Tasks

            -pl_file xyz -> Write out a mh perl code file.  These are the various
                            formats of Calendar subjects:

                              vcr channel_num show_name (e.g. VCR 8 Dilbert)
                              voice_command  (e.g. Christmas lights on)
                              message_to_speak (e.g. Today is national geek day)

                            Note:  If the text is not a vcr or voice_command, it
                                   will be treated as a messages.

            -date xyz    -> Get data with a start_time on date xyz.  Default is today.
            -date_end xyz-> Get data with a start_time between -date and -date_end
            -days xyz    -> Look out xyz days from -date.  Default is 1 day.

          Examples:
            outlook_read -help
            outlook_read -date 1/17
            outlook_read -date 12/25/97 -days 2
            outlook_read -pl_file /projects/mhcode/outlook_events.pl
            outlook_read

    report_weblog
        report_weblog reads MisterHouse and/or Apache server logs and
        generates report graphs and optionally email them.

        Usage:

          report_weblog [options] logfile(s)

            -h        => This help text
            -help     => This help text

            -ignore    xyz => A comma-delimited list of ip address to ignore.

            -mailto     xyz => Will email the charts to xyz
            -mailserver xyz => xyz is the SMTP host.  Default is localhost

            -runid      xyz => All graphs will have xyz as a prefix.  Default is blank.
            -outdir     xyz => All graphs be stored in directory xyz.  Default is .

          Examples:
            report_weblog -mailto 'bruce@misterhouse.net' /var/log/httpd/access_log
            report_weblog -mailto winters@home.net -mailserver 24.2.1.70 e:/mh/data/logs/server.1999_07.log

    mhsend
        mhsend allows you to send data to mh over the intra/internet. Data
        can be logged, filed, spoken, displayed, or run. The data processed
        by mh/code/common/mhsend_server.pl

          mhsend sends data to the MisterHouse program, through the internet/intranet.

          The following flags control what the companion mh server.pl code does:

            -file xyz    -> Files the data into mh/data/mhsend/xyz

            -log xyz     -> Logs  the data into mh/data/mhsend/xyz.log

            -run         -> Runs the data as command.

            -display xyz -> Displays the data.  xyz is how log to leave the display up.

            -speak       -> Speaks and displays the data.

            -pwfile xyz  -> Points to a password file.

          Usage:
            mhsend 'hi there'
            mhsend -display 60 hi there Bruce
            mhsend -port 8083 -host misterhouse.net 'hi there'
            mhsend -speak file_to_speak.txt
            mhsend -file file1 file_to_send.txt
            mhsend -log This is a good URL:  http://goodplace.com
            echo 'hi there' | mhsend -stdin

    monitor_weblog
        monitor_weblog monitors a Apache server log file. When the server
        logs hits, this code will summarize them and pass them onto the
        MisterHouse speak_server.pl code via a tcp/ip socket.

        Usage:

          monitor_weblog [options] logfile

            -h        => This help text
            -help     => This help text

            -mh_server xyz => The ip address of your MisterHouse box.
            -mh_port   xyz => The ip port you set mh.ini server_speak to.
            -ignore    xyz => A comma-delimited list of ip address to ignore.

          Examples:
            monitor_weblog -mh_server house -ignore 'house,10.0.0.1' /var/log/httpd/access_log

    set_clock
        set_clock sets the clock according to the time from an internet
        connected NIST atomic clock server. Instead of requiring accurate
        time zone information, set_clock will simply set the time minute and
        second, but will keep to the nearest hour it was already set to.

          Usage:
            set_clock [options]

          Options:
            -h         => This help text
            -help      => This help text

            -no_set    => Do NOT set the clock, only list the difference in time.

            -ignore xyz=> Do NOT reset the local clock if the time is more then xyz minutes off.
                          Default=10

            -method xyz=> xyz can be inet_time, http, or socket.  Default is socket.

            -server xyz=> xyz is the server to get the clock data from.  Here are a few:
                            time-a.timefreq.bldrdoc.gov:14  (default)
                            time.nist.gov:??
                            time-nw.nist.gov:??

          Example:
            set_clock
            set_clock -server time-a.timefreq.bldrdoc.gov:14

          More info about NIST clock servers if available at:
            http://www.boulder.nist.gov/doc-tour/atomic_clock.html

        Here is an example call from mh. Since this is typically quick, we
        do it here with a 'do' instead of a call with Process_Item:

           if (time_cron '7 6 * * * ') {
             my $status = do "$Pgm_Path/set_clock";
             speak $status unless $Save{sleeping_parents};
           }

    set_password
        set_password creates and/or queries the mh password file, using the
        crypt function. If this password is set, then various menus (e.g.
        web, telnet, wap) will prompt for it. By default, the mh setup web
        pages can only be controled with the admin logon, but you can
        add/delete other commands by adding authority=admin to the
        password_allow_file. You can also specify authority with the
        set_authority object method, or by adding it to your web/bin xyz.pl
        file # Authority=user comment.

        The crypt function limits the password to 8 characters. If -user is
        not specified, it defaults to family.

          Usage:
            set_password [options]

          Options:
            -h         => This help text
            -help      => This help text

            -check     => Will check, not set, the password.  Turned on if -pw_file does not exist.

            -user xyz     => xyz is the user to set the password for.

            -password xyz => xyz is the password to check.
                             If not used, a TK popup window will prompt for it.

            -pw_file xyz  => xyz is the file that the crypt-ed password is read/written to.
                             Default is mh/data/.password

          Examples:
            set_password  -user admin
            set_password  -user family -password xyz

    speak
        speak will pass the file or text to mh. It simply runs the house
        program.

          Example:
            speak "Boo, did I scare you?"

  Authentication
    A one way crypt-ed password authentication scheme can be enabled for the
    web and telnet.pl interfaces.

    The location of the crypt-ed password is controlled with the mh.ini
    password_file parm. The default location is in $Pgm_Root/data/.password.

    If this file does not exist, the telnet.pl and web interface will be
    enable for anyone and everyone. If it does exist, and the correct
    password is not entered, the telnet.pl and web function will not
    implement the requested commands.

    The set_password command is used to create and check this password. If
    you are running the un-compiled perl and do not have Tk installed, the
    password will have to be passed in with the -password parm (set_password
    -h for more help). This can be run from a shell prompt or with the mh
    mh_control "set the password" command.

    If you want to change the password, delete the password file and rerun
    set_password.

    When prompted for UserName and Password from your web browser, you can
    leave UserName blank ... it is not used.

    Netscape appears to allow you to re-try a password, but MS Explorer must
    be re-started if you want to enter a different password.

  Customizing the Menu interfaces
    You can nested create menus that let you query mh data or control mh
    items or commands. You can use any or all of these interfaces to walk
    through menus:

      Web browser:        http://localhost/sub?menu_html
      WAP phone:          http://localhost/sub?menu_wml
      Tellme.com phone:   http://localhost/sub?menu_vxml
      LCD keypads:        see mh/code/bruce/lcdproc.pl for an example
      Audible feedback:   see mh/code/public/audible_menu.* for an example

    To enable, use mh/code/test/menu.pl to read in menu files. The format of
    the menu files is a simple text file, where each record is prefixed with
    a record type character. Here is an example:

     M: Test            # Top level menu can have any name
        D: HVAC
        D: Lights
     M: HVAC
        D: Indoor temperature
           R: At $Time_Date, the temperature is $Weather{TempIndoor}
        D: Living fan [on,off]
           A: set $living_fan $state
        D: Outdoor temperature
           A: What is the outdoor temperature
           R: last_response
        D: Humidity
           A: What is the humidity temperature
     M: Lights
        D: Camera [+,-,^,v]
           A: Camera light [on,off,+30,-30]
           R: eval "Light $state" . chr(07) # Ring bell
        D: Bedroom [on,off,+30,-30]
           P: anyone
        M: Outside
     M: Outside
        P: anyone
        D: Garage Light [on,off,+30,-30]
        D: Backyard Light [on,off,+30,-30]

    These are the record types:

     M:  Menu screen
     D:  text to Display as a menu choice
     P:  Password bypass group
     A:  what Action to run (either a voice cmd or code to eval)
     R:  what Response to display.
         last_response will pick up the last mh response
         no_response   will return no response
         href=url      will goto the url (is using a web browser)
         If not specified, defaults to select state or last_response.
         Begin with eval to eval the string first.

    The above example specifies 3 menu (M:) records that creates a Test
    menu, with 2 submenus, HVAC and Lights.

    The HVAC 'Indoor temperature' display record (D:) will respond (R:) with
    the indoor $Weather data.

    The 'Living fan' entry will evaluate the 'set $living_fan $state' action
    (A:) record to set the fan to the specified (on or off) state.

    The 'Outdoor temperature' record will run a Voice_Cmd action (A:), and
    respond with the last data printed or spoken by mh (the results of the
    outdoor temperature command). The 'Humidity' record will also run a
    Voice_Cmd and default to last_response.

    The 'Camera light' record also runs a Voice_Cmd action. To save (LCD)
    display space, the on,off,+30,-30 Action states were renamed on the
    Display record to +,-,^,v.

    The 'Bedroom' record matches a Voice_Cmd exactly, so no action record is
    required. It specifies a Password bypass group of anyone, meaning anyone
    can run this command without having to use a password.

    The last Outside Light records are a 3rd level menu, nested under the
    2nd level Lights menu. There are no limits to how deep you can nest
    menus and they can appear in any order you want in the file. The same
    menu can be a submenu of more than one other parent menu.

    The Outside menu specifies a P: record before any items are specified,
    so that is the default for all items on this menu, meaning anyone can
    run the commands on this page without a password.

    If you want a starting point to customize your own menus, you can find
    an auto-generated menu for all the mh Voice_Cmds in the mh_temp.menu
    file in your code directory. This file is re-generated on reload, so
    copy and edit this file, then point menu.pl at your edited copy.

    You can have different sets of menus, for use with different interfaces
    or users. For example, the default menu.pl looks like this:

        my $menu_mh   = menu_create "$config_parms{code_dir}/mh_temp.menu";
        my $menu_test = file_read   "$config_parms{code_dir}/test.menu";
        menu_parse $menu_test, 'default';
        menu_parse $menu_mh,   'mh'; # This menu has all the mh voice commands

    menu_create creates the example mh_temp.menu file for all voice commands
    and returns it into $menu_mh. $menu_test has the test menu, similar to
    the above example. You can specify the menu group (e.g. default or mh)
    when calling any of the menu interface subroutines. For the above
    menu.pl, you can point your WAP phone to:

      Either of these will get the 'default' test menu:
        http://misterhouse.net:8090/sub?menu_wml
        http://misterhouse.net:8090/sub?menu_wml(default)

      This will get the auto-generated 'mh' entry for all voice commands:
        http://misterhouse.net:8090/sub?menu_wml(mh)

    Something like this could be used to create different menus for
    different LCD controllers:

      $menu_standard = file_read 'standard.menu';
      $menu_bedroom  = file_read 'bedroom.menu';
      $menu_living   = file_read 'living.menu';
      $menu_all      = file_read 'all.menu';

      if ($Reread) {
        menu_parse $menu_bedroom  $menu_bedroom . $menu_standard,  'bedroom';
        menu_parse $menu_living   $menu_living  . $menu_standard,  'living';
        menu_parse $menu_all      $menu_all     . $menu_living .
                                  $menu_bedroom . $menu_standard, 'all';
        $lcd_data{bed1}   {menu_group} = 'bedroom';
        $lcd_data{living1}{menu_group} = 'living';
        $lcd_data{living2}{menu_group} = 'all';
      }

    To test your menus, point your browser to
    http://localhost:8080/sub?menu_html To view auto-generated menus, try
    http://localhost:8080/sub?menu_html(mh)

    If you have a LCD supported by lcdproc program (
    http://lcdproc.omnipotent.net ) (e.g. crystalfontz or matrix-orbital),
    you can use mh/code/bruce/lcdproc.pl to display menus. Currently only
    the linux version of lcdproc supports the keypad option on the
    maxrix-orbital displays. Several other mh'ers are writing similar code
    for their LCD displays.

    If you have a 24x7 internet connection, you can walk the menus with
    either a WAP compatible cell phone, or using voice commands with any
    phone via a Voice Portal like http://studio.tellme.com.

    For cell phones, simply point your phone to
    http://your_domain/sub?menu_wml. You can test your menus with a web
    based phone simulator at http://www.yospace.com or download a phone
    simulator from http://phone.com . Other browsers are listed at
    http://www.palowireless.com/wap/browsers.asp , although I didn't have
    much luck with http://www.wapsilon.com or /http://www.gelon.net .

    Tellme.com currently offers free 1-800 access to their VR and TTS
    engines, using vxml (Voice XML) menus. You can view the mh vxml menus by
    pointing a XML compatible browser (e.g. IE) to this address:

       http://localhost:8080/sub?menu_vxml

    When ready to try with tellme, sign up for a free developer id, which
    will be your extension number. I used Home0 (46630). Then you can go to
    my studio and enter your URL:

       http://studio.tellme.com/mystudio/mystudio.cgi

       http://your_domain/sub?menu_vxml

    where your_domain is your domain name or ip address. To test, dial:
    1-800-555-8965 (VXML) and enter your developer ID and Pin. When you have
    it working, click on the MyExtentions, enter the same URL and check the
    enable checkbox. Now you can dial:

       1-800-555-8355  (wait for 'tellme more') 1-yourextention

  Customizing the Tk Interface
    You can modify what the Tk window displays by modifying the tk_frame.pl
    and tk_widgets.pl members. tk_frame.pl controls the general layout and
    size of the various scrolled lists. Modifying this member takes a little
    knowledge of perl Tk.

    tk_widgets.pl shows how you can add widgets to display various info and
    allow for buttons for manual control. The mh tk_xyz subroutines used
    here will add widgets to either the menu_bar or to the 'grid' frame
    defined in tk_frame.pl.

    Any change to tk_frame.pl or tk_widgets will be evaluated on a code
    reload, as will the tk_geometry parm in mh.ini.

    tk_xyz widgets can be added anywhere in your mh code, not just the
    tk_widgets.pl member. See the weather_monitor.pl, internet_mail.pl, and
    tk_eye.pl members in mh/code/bruce for examples.

    The tk_xyz widgets are 'packed' into the menu_bar or grid in the order
    that they are evaluated. You can use the 'Position' directive at the top
    of your member.pl code to control this. Position=1 is reserved for the
    tk_frame.pl member, as this must be evaluated first.

    Note, you can also use native Perl tk commands, or create or override
    the tk_xyz subroutines in your own code for more flexibility.

    For more info on the tk_xyz widgets, see look in the mh functions
    section of this document.

  Customizing the Web Interface
    You can write your own Web page interface. The examples under mh/web are
    frame html files that dictate the shape and positions of various
    mh-generated html frames.

    You can use the html_root and html_file mh.ini parms to point to the
    directory and default web page. The html_default parm are the member
    names used when the URL points to a directory.

    The html_style mh.ini parm points to a style sheet that will be loaded
    for all mh-generated pages. Style sheets allow default control of colors
    and fonts.

    There are other, html_* parms in the mh.ini file to control how the
    auto-generated html data looks. For example, you can control table sizes
    and auto refresh rates. See the mh.ini file for more info.

    If you want to enable MsAgent support, use the mh.ini
    html_msagent_script* parm. MsAgent allows remote IE web browsers to do
    TTS and VR on the remote box. Note, you can do this even if you are
    running mh on a linux box as long as your remote box is running IE. To
    enable, add the new mh.ini html_msagent_script* parms that points to a
    mh/web file with the agent code (currently only Jeff's miniJeff agent is
    available). Then click on the MSAgent checkbox at the top of the Web
    menu to turn him on or off.

    IE, by default, will run each command only once, unless you change this
    setting: Tools, Internet Options, General, Settings button on Temporary
    Internet Files, under "Check for new versions of stored pages" select
    the option "Every visit to the page."

    The web Items and Groups displays will use icons to indicate the object
    state. Icons will be searched for in the mh/graphics directory according
    to the objects icon method, the object name, the object state, and the
    object type. For example, given this code:

        $fountain = new X10_Appliance('C3');
        set_icon $fountain 'water';

    The search order for the ON state will be:

        water-on.gif
        fountain-on.gif
        x10_appliance-on.gif
        on.gif

    The web Category display will try to match icons to Voice_Cmd objects
    using the icon method, object name, voice command text, and filename.
    For example, given this code from the member internet_data.pl:

     $v_set_clock = new  Voice_Cmd('Set the clock via the internet');
     set_icon $v_set_clock 'time';

    The search order will be:

     Look for icons that match time
     Look for icons that match set_clock
     Look for icons that match words in 'Set the clock via the internet'
     Look for icons that match internet_data.pl

    All icons in the graphics directory are compared to each of the above.
    The first match (by search order) is used. If there are multiple
    matches, the match with the longest word is used. For example, if we had
    icons names 'clock.gif' and 'internet.gif' (but no 'time.gif'),
    'internet.gif' would be used.

    In all of the above examples, .gif was used as an example, but any
    browser compatible graphics file can be used (e.g. .png and .jpg). If
    you want to add new icons, some good sources are at
    http://www.freegraphicland.com/ , http://www.graphsearch.com/ ,
    http://www.add-soft.com/icons/index.html ,
    http://www.rad.kumc.edu/icons/icons.htm and
    http://www.engineeringplastics.com/HomeSeerIcons/

    Important note: If you do play with adding/deleting/renaming icons, you
    will want to do a reload before refreshing your browser. The icon
    directory is cached by mh (for efficiency). If no code has changed, the
    reload will run very quickly.

    You can use the following html addresses to have mh return dynamically
    generated html:

      /category
        This will list all the Voice_Cmds, sorted by categories.  Categories are the member names of the
        user code, or the value in the Category=value field specified in the user code.
        Items are also listed by Group, and Object Type

      /list?Category
        This can be used to list just the commands in the specified Category.

      /list;h_response?Category
        As above, but it also passes h_response to the RUN commands (see h_response below)

      /group
        This will return all the Group Items

      /group?$group_name
        This will return all the objects in $group_name

      /items
        This will return all the non Voice Objects (e.g. X10_Items)

      /items?object_type
        This will return all the objects of type object_type

      /widgets
        This lists all the Tk widgets.

      /widgets_label
        This returns just the label widgets

      /widgets_entry
        This returns just the entry widgets

      /widgets_radiobutton
        This returns just the radiobutton widgets

      /widgets_checkbox
        This returns just the checkbox widgets

      /speech
        This lists the most recently spoken text

      /print_log
        This lists the most recently print_log text.

    If you want to generate your own html on-the-fly (like a cgi program
    would), instead of pointing to a .html file or one of the above
    pre-defined mh-generated lists, point to a .pl file and have that perl
    code return the desired html. Note, this currently differs from how a
    classic cgi program would work (they return the html as STDOUT). Some
    examples of this can be found in the mh/web/bin/*.pl files.

    If you want to run with a classic cgi that return html so STDOUT,
    specify #!/local/bin/perl on the first line of your file. An example is
    in mh/web/bin/test_cgi.pl

    One other note on .pl web files. You can have them use variables you
    define in your mh user code, but only if those variables are defined
    with "use vars '$my_var1', '$my_var2'". Variables defined with "my
    ($my_var1, $my_var2)" are local only to the mh loop, and would not be
    available to any web .pl program. To pass arguments to your .pl file,
    append them to the URL in normal CGI fashion after a ?. For example:

       http://misterhouse.net/test/test1.pl?argument1&argument2

    In addition to calling the above mh-generated html directly, you can
    embed these lists in your own html using the server-side-include syntax.
    If the extension on your html is .shtml (for server-side html) then mh
    will parse the html, looking for the following string:

       <!--#include file="your_directive"-->

    Anytime the above string is found, it will replace that record with
    whatever you specify in "your_directive". "your_directive" can either be
    another html (or shtml) file, or it can be one of the mh-generated lists
    from above. For example:

      <!--#include file="/mh/other_links.html"-->
      <!--#include file="category"-->
      <!--#include file="/mh_default/test/test1.pl"-->

    You can use an #include var= directive, like file= above except it
    returns the contents of an mh variable. For example:

      <li><b>Version:</b> <!--#include var="$Version"-->

    or

      Missing page: <!--#include var="$Misc{missing_url}"-->

    mh global variables (including %Save and %Misc values) are accessible to
    .shtml pages. To make other variables accessible to .shtml pages you
    need to declare them with use vars '$variable_name'.

    If you use svar=, instead of var=, the var contents will be returned
    only if the web user is authorized (i.e. logged in or from an ip address
    listed in password_allow_clients).

    This what is used in the mh 'About' web button to return the mh version.

    You can also define your own functions in your code files to return
    html, and then call them with the code= include directive or as a
    h_response to a /SET or /RUN command (see below). For example, add these
    to any of your code files:

      sub web_func1 {
         return "uptime = " . &time_diff($Time_Startup_time,$Time , undef, 'numeric');
      }
      sub web_func2 {
         my ($arg1, $arg2) = @_;
         return "results from function 2: $arg1, $arg2";
      }

      # Add this if you want unauthorized web access to these functions
      if ($Reload) {
        $Password_Allow{'&web_func1'} = 1;
        $Password_Allow{'&web_func2'} = 1;
      }

    Then call them like this:

     <p>Test web functions
     <!--#include code="&web_func1"-->
     <!--#include code="&web_func2(myarg1,myarg2)"-->

    Or with this:

     http://localhost:8080/SET;&web_func2(myarg1,myarg2)

    One example of a built in function is html_item, which will return html
    for one object. For example:

      <!--#include code="&html_item('v_what_speed')"-->
      <!--#include code="&html_item('mode_mh')"-->

    Another useful built in function is &dir_index, which can be used to
    create a directory listing of files in selected directories. For
    example, if you have a web directory called pictures, create an
    index.shtml file, and include the following line:

        <!--#include code="&dir_index('/pictures','name',0)"-->

    You can have &dir_index sort by name, type, size, or date. Set the 3rd
    argument to 1 for reverse sort order. An optional 4th parameter is a
    regular expresion that can be used to subset to matching files. See
    mh/web/graphics/index.shtml and mh/data/email/index.shtml for examples.

    If you want to have only a few specific html controls, you can write
    your own html using the RUN and SET (or SET_VAR) URL keywords to control
    mh. Here are a few examples:

     <a href=http://localhost:8080/SET?$test_lights?off >Turn the test light off</a>
     <a href=http://localhost:8080/SET?$Save{test_input1}?abc >Load test_input1 with abc</a>
     <a href=http://localhost:8080/RUN?WebCam_light_on >WebCam light on</a>

    The SET (or SET_VAR ... they are now the same) command can set any
    object or variable defined in your user code. The RUN command can run
    any voice command. Use '_' to replace blanks in the command string.

    SET, RUN, or SUB can use an optional h_response field to decide what
    html will be returned:

      Format:
       SET;h_response?$object?value
       RUN;h_response?command
       SUB;h_response?sub_name(arg1,arg2)

      $var and $object are the variable and object names, with or without the $ prefix.

      h_response is optional and can be one of the following:
       &function        The results of &function will be returned
       &function(args)  The results of &function(arg) will be returned
       no_response      Tells the browser to do nothing (page is not changed).
       last_response    The last displayed or spoken text.
       last_response_## As above, but only the first ## characters.
       last_displayed   The last displayed text will be returned
       last_spoken      The last spoken item will be returned
       filexyz.html     The contents of filexyz.html will be returned
       string           string will be returned
       referer          The browser will be re-directed back to the calling page
       referer/URL      The browser will be re-directed to the referer_root/URL
       &referer(URL)    The browser will be re-directed to the referer_root/URL.
       http://url       The browser will be re-directed to URL
       blank:           A list of recently spoken text will be returned

    Here are some examples demonstrating these options:

     This returns the last spoken line:
      <li><a href=http://localhost:8080/RUN;last_spoken?WebCam_light_on>Light on</a>

     This returns the string "Thanks for playing"
      <li><a href=http://localhost:8080/RUN;Thanks_for_playing?WebCam_light_off>Light off</a>

     This returns the contents of the mh/web/test/test3.html file:
      <li><a href=http://localhost:8080/RUN;test/test3.html?WebCam_light_+50>+50</a>

     This returns a list of recently spoken text:
      <li><a href=http://localhost:8080/RUN?WebCam_light_-50>-50</a>

     This will allow for text input, using a html FORM, and returning the results
     form the user defined function &my_response (Note, to give anyone access to this
     function you will need to authorize &my_response with this in your code:
     $Password_Allow{'&my_response'} = 1):

      HTML:
       <form action="SET;&my_response">Test 2<input size=9 name="$test_form"></form>

      User code:
       $test_form = new Generic_Item;
       print_log $state if $state = state_now $test_form;
       sub my_response {
         return "You entered " . state $test_form;
       }

     This will set a $barcode_scan Generic_Item, then display a web page
     that was updated by user code:

      <FORM ACTION="SET;barcode_search.html"  target='speech'>
      <p>Scan data into here: <INPUT SIZE=60 NAME="$barcode_scan" value=""></FORM>

     This will redirect the browser back to the calling page
     (note the $ from the $camera_light object is optional):

      <a HREF="http://misterhouse.net:8090/SET;Referer?camera_light=on">Light on</a>

     This will redirect the browser to mh page ia5/lights/main.shtml page:

      <a HREF="http://misterhouse.net:8090/SET;referer/ia5/lights/main.shtml?$camera_light=on">Light on</a>

     This will redirect the browser to misterhouse.net

      http://localhost:8080/RUN;http://misterhouse.net?test_volume_at_60

    You can also use RUN;h_response without a command. For example, if you
    want to get a directory listing, sorted by date (recent on top):

      <a href=http://localhost:8080/RUN;&dir_index('/pictures','date',1)>List pictures by date </a>

    Here is an example of using a subroutine to generate html:

       http://misterhouse.net:8090/SUB;tellme_menu(,,html)

    You can also use a separate web server (e.g. Apache or ISS) to serve
    pages that control MisterHouse. For example:

      Turn lamp
      <a href="http://localhost:8080/SET;referer?$light1?on">on</a>
      <a href="http://localhost:8080/SET;referer?$light1?off">off</a>

    To enable on-the-fly icons to that show current item states, add this
    function to your code:

     sub web_icon_state {
         my ($item) = @_;
         my $obj   = &get_object_by_name($item);
         my $state = $obj->state;
         my $icon  = "$main::config_parms{html_dir}/graphics/$state.gif";
         print "db icon=$icon s=$state i=$item\n";
         my $image = file_read $icon;
         return $image;
     }

    Then add this to your html:

      <img src=http://localhost:8081/sub?web_icon_state('$light1')>

  Voice Recognition (VR) and Text To Speech (TTS) on windows
    VR and TTS options are controlled with the Microsoft Voice application.
    It shows up as a 'green V' in your icon tray. Right clicking on this
    icon brings various menus for configuring your VR and TTS options.

    You can pick between 3 VR modes, by right clicking on the MS Voice icon,
    or toggle between the modes by left clicking on the icon.

      - Listening for Voice Commands

        This mode will listen to anything and everything.  You can get lots of false
        recognition in noisy environments in this mode.

      - Not listening

        In this mode, VR will be turned on only when you hold down an activation key or
        move the mouse to a screen corner, depending on which 'Listening Mode' you
        pick in the 'Voice Command Options' menu.

      - Paused listening

        This mode only listens for a specific trigger keyword.  If heard, then it
        temporarily switches to normal listening mode. The trigger keyword is listed
        as your 'Computer Name' in the 'Voice Command Options' menu.

    You can improve your Voice Recognition accuracy by talking through a 10
    minute training session. Right click on the MS Voice icon and pick
    'Voice Command Options', then click on the 'Advanced' tab, and finally
    click on the 'Optional Training' button.

    You can pick the voice and speed of the TTS by clicking on the 'Computer
    Voice' tab on the same 'Voice Command Options' dialog.

  Voice Recognition (VR) and Text To Speech (TTS) on Unix
    Need notes on how to use ViaVoice on linux and how to enable different
    TTS engines with Festival.

    There is a now also a Festival lite ending that looks promising at
    http://www.speech.cs.cmu.edu/flite/

    For vr with ViaVoice, use mh/code/bruce/viavoice_control.pl

  Using distributed MisterHouse proxies
    There are several reasons you might want to run one or more extra proxy
    versions of MisterHouse:

     - Allows the real real mh to not pause while reading or
       writing to slow interfaces (e.g. X10 CM11, CM17, or iButton).

     - You can monitor and control remote serial ports.  For
       example, an old PC running MisterHouse in a barn,
       or an internet connected PC in a vacation home.

     - Sharing of interfaces between different mh boxes.
       For example, you can run a test version of MisterHouse
       to debug new code without buying a 2nd interface.

     - Use small, quiet, low power PC's in differnet rooms to
       distribute speech from your main MisterHouse with
       the speak rooms= parm, using eithernet instead of a
       relay / PA wiring scheme.

     - Wean yourself off of one OS onto another, sharing ports
       from your old computer to the new one.

    To run a proxy mh, copy and modify the mh/bin/mh_proxy.ini and mh_proxy
    (unix) or mh_proxy.bat (windows) files. Then run mh_proxy. If you have
    tk installed, you may want to run mh_proxy -tk 1 while debugging.

    Another option, instead of running a simple pruned down mh with
    mh_proxy, you can also simply run a 2nd normal version of mh, and simply
    include the proxy code dir in you mh.ini code_dir parm and add the
    server_proxy_port parm. For example:

     code_dir = c:/misterhouse/my_code,c:/misterhouse/mh/code/proxy
     server_proxy_port = 8085   # Used by the proxy_server.pl

    On your main mh box, change your mh.ini port parms to point to the
    address:port of the proxy box, like this:

     cm11_port           = proxy localhost:8085
     cm17_port           = proxy localhost:8085
     iButton_serial_port = proxy localhost:8085

    You can start or restart either the main mh or mh_proxy before or after
    the other one and they should sync up. Tested interfaces include cm11,
    cm17, iButton, weeder, mr26, wx200 weather, modem, netcallerid, and ham
    tnc.

    If you want to put an iButton port on a proxy, you will need to also run
    a code file on the proxy that defines all the iButtons you will use on
    that port. These iButton object names must match those on in your real
    code on your main mh box. One way to do this is to share the same
    iButton.pl code file on your proxy as you use on your main mh box, using
    the mh.ini/mh.proxy.ini code_dir and load_code parms. For example:

     main mh:
      code_dir  = c:/misterhouse/bruce

     proxy mh:
      code_dir  = $Pgm_Root/code/proxy,c:/misterhouse/bruce
      only_load = proxy_server.pl,ibutton.pl

    If you want to distribute speech to mh_proxy machines, see
    mh/code/public/speak_proxy.pl for example code.

    Here are some small $400->$500 computers that might be good for
    distributed speech and/or ports:

     http://www.cappuccinopc.com/espressopc.asp  (6"x5"x1.5")  (0.9Lbs) .

     http://216.136.224.156/hoct12/minpcpeniii1.html  2.2 Ibs Dimensions: 6" x 5.75" x 2.25"

NOTES
    This section has info that doesn't fit well anywhere else. Probably be
    better as a FAQ.

    My hope is that mh will grow into a group project. There is so many,
    almost unlimited possibilities in Home Automation, that no one person
    could hope to implement them all.

    If you feel so inclined, please send me any code that you develop that
    you think others might be interested in, and I'll include it in the
    distribution.

    If you would rather see it mh in your windows tray, rather in the
    taskbar, here is a useful utility that will let you move any program to
    the tray: http://www.teamcti.com/TrayIt

    Another handy utility for windows 95/98 users will help you monitor how
    much CPU and memory mh takes. TaskInfo80, available at
    http://www.iarsn.com/index.html

    If you are getting the message "Out of environment space" try adding
    these records:

      config.sys:   shell=command.com /e:8000 /p
      autoexec.bat: set comspec=c:\command.com

    By default, declaration code like '$object = new ...', or 'my $var =
    ...' is pulled out of the user loop code and put in the startup code, so
    it is only run once. If you want to force additional code out of the
    loop code, you can use add # noloop=start/stop comments before and after
    the code segment, or you can add a # noloop comment to the end of the
    record. For example:

                     # Example of noloop record comments
      my $weather_wind_gust_threshold=$config_parms{weather_wind_gust_threshold}; # noloop
      $weather_wind_gust_threshold=12 unless $weather_wind_gust_threshold;        # noloop

                     # Example of noloop block comments
      # noloop=start
      my $mp3names;
      while ( my $mp3name = <d:/library/*.m3u> )
       {
        $mp3name =~ s#^.*/##;  # remove path
        $mp3name =~ s#\..*$##; # remove extension
        $mp3names .= "," if $mp3names;
        $mp3names .= $mp3name;
       }
      # noloop=stop

      $v_play_music = new Voice_Cmd("Play [$mp3names]");
      if ($state = said $v_play_music) {
        ... more code here ...

    If you want to have a user defined function called once perl loop, but
    you want it before or after all the other user code has been called, use
    the &MainLoop_pre_add_hook and &MainLoop_post_add_hook functions to add
    callbacks to your functions. For example:

      $v_hook_pre_add   = new Voice_Cmd 'Add  pre  code hook';
      $v_hook_pre_drop  = new Voice_Cmd 'Drop pre  code hook';
      $v_hook_post_add  = new Voice_Cmd 'Add  post code hook';
      $v_hook_post_drop = new Voice_Cmd 'Drop post code hook';

      &MainLoop_pre_add_hook(  \&test_hook_pre)  if said $v_hook_pre_add;
      &MainLoop_pre_drop_hook( \&test_hook_pre)  if said $v_hook_pre_drop;
      &MainLoop_post_add_hook( \&test_hook_post) if said $v_hook_post_add;
      &MainLoop_post_drop_hook(\&test_hook_post) if said $v_hook_post_drop;

      sub test_hook_pre  { print "<"; }
      sub test_hook_post { print ">"; }

    If you want your hook code to last between code reloads (e.g. added by
    startup code from a module), set 2nd argument to 'persistent' (a value
    of 1 will also work ... grandfathered in for old code). For example:

      &main::MainLoop_post_drop_hook( \&jabber::process, 'persistent' );

    If you want that hook to run first, before all other hooks of that type,
    set the 2nd argument to 'first', or 'persisent_first'. Otherwise the
    hook is pushed to the end of the list, so it is run in the order the
    hooks were added.

    Here is a list of all the code hook locations:

      MainLoop_pre   => Run before user code
      MainLoop_post  => Run before user code
      Serial_data    => Run on any incoming serial data
      Serial_match   => Run when incoming serial data matches and object
      State_change   => Run when objects change states
      Play_parms     => Run before playing wav files, so you can set play parms
      Play_pre       => Run before playing wav files
      Play_post      => Run after  playing wav files
      Speak_parms    => Run before speaking text files, so you can set speak parms
      Speak_pre      => Run before speaking text files
      Speak_post     => Run after  speaking text files
      Log            => Run on all print_log, display, speak, and play calls.
      Reload_pre     => Run before reload
      Reload_post    => Run after  reload
      Exit           => Run on exit

    If you want to use a hook to modify parameters for the subsequent mh
    function, use a _parm hook, which passes parms in by reference rather
    than by value. For example, this will add a to_file option to all speak
    calls:

      &Speak_parms_add_hook(\&speak_parm_update) if $Reload;
      sub speak_parm_update {
          my ($parms_ref) = @_;
          $$parms_ref{to_file} = "somename.wav";
      }

    For more examples on code hooks, see mh/code/examples/test_code_hooks.pl

KNOWN LIMITATIONS AND BUGS
  General bugs/limitations
    The JDS and Marrick interface modules are untested.

    mh also has the 'windows 49.7 day bug' (2**32 milliseconds) that has
    been in the news lately:
    http://www.news.com/News/Item/0,4,33117,00.html?st.ne.ni.rel .

    Under windows, TK windows do not get focus and some keys and shortcuts
    do not work. Worse yet, the 'tear off' line (the dashed line on the pull
    down menus) causes mh to hang. Perl/tk on Unix does not have either of
    these problems. Hopefully this will get fixed in a future release of
    perl/Tk. Note: if you have an old perl/tk (older than 8.0012), things
    are even more messed up.

  Windows bugs/limitations
    If 'run' calls give you an 'out of environment memory' error, create a
    command.pif and change the memory, initial environment from auto to 4k.

    Activestate perl 5.6 build 613 and 616 leaks memory on windows. On my
    box about 30 Meg, once a day. Displaying .gif and .jpg files also leaks
    about 10x the picture size (e.g 10 50k photos -> 5 meg).

    Adding, deleting, or changing Voice commands does is not recognized by
    the Windows MSVoice application while it is running. You have to exit
    and restart MSVoice, then restart mh to get it to recognize new
    commands. Hopefully we can get this to work with release 5.0 of the MS
    Speech SDK.

    If you have perl installed, you may get this message:

      Can't find 'boot_IO' symbol ...

    The problem is there are two different IO.DLL files, one in the FTP
    module and one in the TK module. Do a find in IO.DLL in your perl
    directory, and rename the one in the tk path.

  Unix bugs/limitations
    Voice Recognition IS supported under linux using IBM's ViaVoice SDK.
    Potential candidates for VR engines other non-linux Unix platforms are:

         http://www.speech.cs.cmu.edu/sphinx/
         http://cslu.cse.ogi.edu/index.html
         http://WWW.ISIP.MsState.Edu/projects/speech_recognition/
         http://www.tmt.de/~stephan/ears.html

    Disk drive info (need to port from DriveInfo to df)

    Linux KDE users should uncheck "Apply fonts and colors to non-KDE apps",
    or the black text on white windows shows up as white text.

AUTHORS
    Bruce Winter: bruce@misterhouse.net , http://winters.ws

SEE ALSO
    mh can be download from http://misterhouse.net

    Here is a list of articles written about MisterHouse:

      http://www.hometoys.com/htinews/jun99/articles/winter/winter.htm

      http://linuxworld.com/linuxworld/lw-1999-10/lw-10-mrhouse.html

      http://www.faribault.k12.mn.us/brian/aprs/

      http://www.circuitcellar.com/pastissues/articles/winter111/winter.pdf

      http://www.itknowledge.com/tpj/issues/vol5_1/tpj0501-0006.html
        (User login to The Perl Journal is required)

    The Circuit Cellar Ink article includes a diagram of how to set up a
    house PA system, so you can control which rooms MisterHouse talks to.

    You can subscribe and/or view the mailing list at
    http://sourceforge.net/mail/?group_id=1365 You can view the archive at
    http://lists.sourceforge.net/pipermail/misterhouse-users/ and search it
    at http://lists.sourceforge.net/mailman/listinfo/misterhouse-users

    An older list with a searchable archive at <a
    href=http://www.onelist.com/messages/MisterHouse>here</a>).

    Jan Dubois has a fun page of other ticks you can do with perl on windows
    at
    http://opensource.activestate.com/authors/jandubois/Perl/TPC3/fun.html

    The comp.home.automation newsgroup is another handy resource, especially
    for X10 related questions.

    Neil Cherry has created a home for linux related Home Automation
    programs at http://linuxha.sourceforge.net . This project is developing
    daemons for various interfaces (e.g. ADI Ocelot, CPUXA, and HCS II) that
    hopefully we can then link to with mh.

    Rene Mueller has a nice set of web pages with lots of info on
    HomeAppliances at http://the-labs.com/HomeNetwork/

COPYRIGHT
    Copyright (C) 1998-2001 Bruce Winter. All rights reserved.

    This program is free software; you can redistribute it and/or modify it
    under the terms of the GNU public license.

DISCLAIMER
    This program will most likely not cause your house to self destruct, but
    if it does, please don't call my lawyer. Actually, I suppose you could
    try to call my lawyer, since I don't have one I don't think you will get
    too far (grin).

