mh - MisterHouse Home Automation Program


mh is a perl program for time, event, web, socket, and voice based home control functions. It currently runs on Windows >= XP and on Unix platforms, including Linux and Mac OSX.

perl objects are used for various types of items 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:


This will only run the 3 specified members:


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 -debug serial > test1.log


The system requirements and installation instructions are detailed in /docs/install.html

There is also a FAQ that covers misc. topics in /docs/faq.html

We have a wiki at for user-supplied documentation.

Gordon Meyer wrote a nice getting started article.


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 in Bruce's 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 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) item or Festival socket port
       Setup the OLE Voice Recognition item or socket to the viavoice_server on linux
       Setup the serial items
       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 50 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 . The reference book is "Programming Perl, 3rd edition" by Larry Wall, Tom Christensen, & Randall Schwartz. ISBN 1-56592-149-6. It is available from . "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:

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";
      print "this";
      print " will not run\n";
      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 item:
      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

The list of supported hardware interfaces is now maintained on the Misterhouse wiki at .

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:


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.


Points to where the web interface html files are.


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 ).


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


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


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

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

The complete list of global variables can be found on the Widgets menu of the default web page or at the top of /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.

These constants are defined, so we can use them without quoting them (e.g. if $state eq ON instead of if $state eq 'on'):

  ON      => 'on';
  OFF     => 'off';
  DIM     => 'dim';
  BRIGHTEN=> 'brighten';
  TOGGLE  => 'toggle';
  STATUS  => 'status';
  OPEN    => 'open';
  CLOSE   => 'close';
  OPENED  => 'opened';
  CLOSED  => 'closed';
  MOTION  => 'motion';
  STILL   => 'still';

List of items and their methods

The Misterhouse documentation refers to perl objects as items. Most of these are derived from Generic_Item.

The old list of items is here. These will be moved to the new list soon.

The new list of items is here. This documentation is maintained in the .pm files along side the code.

The list of modules that are not items is here.

List of functions


Checks to see if identified device is already in the chain of set_by devices. Basically identifies never ending loops of setting objects.


If no_strip is set to true:

The original object which initiated this action, OR the same set_by object if it appears in the set_by list already.

If no_strip is set to false or undef:

Undef, unless would return something which is not an object, then it returns the text preceding a [, such as web, email, ...


Assigns a sound file to an event name. This allows us to create and review event sounds. See mh/code/common/ for an example.


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

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


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

Converts from degrees Kelvin/Centigrade to Farenheight


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

Here is a complete list of conversion functions:

  convert_k2f        # Convert degrees Kelvin to Farenheight
  convert_c2f        # Convert degrees Celsius to Farenheight
  convert_f2c        # Convert degrees fahrenheit to celsius
  convert_f2k        # Convert degrees fahrenheit to kelvin
  convert_in2mb      # Convert to inches of mercury to millibars
  convert_in2mm      # convert inches (per hour) to millimeters (per hour)
  convert_km2mile    # convert kilometers (per hour) to miles (per hour)
  convert_mb2in      # Convert millibars to inches of mercury
  convert_mile2km    # convert miles (per hour) to kilometers (per hour)
  convert_mm2in      # convert millimeters (per hour) to inches (per hour)
  convert_mps2mph    # convert meters per second to miles per hour
  convert_mps2mph    # convert miles per hour to meters per second

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


    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.

    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"


              $cid_number, "$Time_Now $Date_Now $Year name=$cid_name");
    See display_callers for an example of how to read dbm files.

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

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

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

    ($count_searched, $count_matched, %results) = search_dbm($dbm_file, $search_string);
  See mh/code/bruce/ and mh/bin/display_callers for examples.

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;
      $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.


    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.


Use this to run some code after some delay.

     eval_with_timer $code, $time;


    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 item state, use the set_with_timer method.


Renames or optionally copies a file to the same name with the "backup1" extension. If the backup1 file already exists, it is renamed to backup2 first, backup2 is renamed to backup3, backup3 is renamed to backup4, and backup4 is deleted.


    file_backup $log_file, "force";
    file_backup $parm_file, "copy";

Normally, this function will only run if the file is older than ten minutes. Use the "force" option if you want to override this behavior. Use the "copy" option if you want to copy the file instead of renaming it. The "copy" option implies "force".


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.


    file_cat $file, $file_total;
    file_cat "$config_parms{html_dir}/aprs/week2.html",
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).


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

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

     file_default $file, $default;


    $f_insult = new  File_Item(&check_default_file($config_parms{speak_insult_file},

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


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

Returns the first few lines of a file


     @data = file_head($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.

Returns the size of a file, in bytes.


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


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

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


    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;

Returns the last few lines of a file


     $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.

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).


    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;

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


     file_write($file, $data);

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


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

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

    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.


   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");

These functions can be used to manipulate mht files.


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
                        # 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 => ',kitchen', file => '../sounds/hello_from_bruce.wav';

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.


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


   $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

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 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


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

                   # 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";

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

      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])
   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);

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 => '', 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 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}");

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, 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.
    $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/

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

    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";

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.

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

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

    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).


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.

                 # 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;

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.


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

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.


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

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:

     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";

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.

     time_random($cron_spec, $frequency)
              # 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)

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.

     time_random_offset($time_now_spec, $offset)
    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));

This function will add or edit Misterhouse parameters in the user's ini file. It will make a backup of the ini file, and it doesn't remove any comments in the file. The first argument is a hash of parameters to set. The second (optional) argument is the ini file you want to modify, and the third (optional) argument is set to 1 if you want to log the change.


    write_mh_opts({"photo_dir" -> $state}, undef, 1);

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


    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).

List of tk widget functions

The tk widget functions are documented here.

List of trigger functions

The trigger functions are documented here.

List of Internet functions


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.


    my $html = get '';
    Other examples are in mh/code/bruce/

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).


    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;

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

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. '' 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.


                           # 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 '';
#  Results: $name = '', $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', ''");
  print_log "Domain=$state" if $state = net_domain_name 'test';

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


    &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/


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.

    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)
     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 => '',
        user => 'anonymous', password => '');
     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 " .
          "-user anonymous -password bruce\";
      start $p_test_ftp;
    print_log "Ftp command done" if done_now $p_test_ftp;

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


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

    net_socket_check($host_port, $protocol);


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

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 . Tik, a tk based client for use on a Unix os, can be found at . You can register for an AIM name at .

Information on Jabber clients (many open sourced clients are available for different platforms AND the protocol is open) can be found at and

    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.
        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.


    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/


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.

    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.
                    Can also be html_inline if you want the html inline, rather than attached.
        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.


    net_mail_send(text => "Test email sent at $Time_Now\n\n");
    net_mail_send(account => 'Bruce', to => '', text => $msg);
    net_mail_send to => ',', 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_email -subject "test" -text "Test background send_email"';

Returns the number of email message on the specified account.

    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.


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

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/ for an example.


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


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 sends alphanumeric page.
    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 walks directories and stores selected files to gziped tar files.

   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 // 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


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

     display -options  text_or_file
    -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


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/

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

find_files finds files on shared networked Windows and samba directories

   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 finds programs running on networked Windows computers.

Requires WMI to be installed on both/all computers.

   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
    -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"


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

     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
     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
     This has a string of digits, one for each account,
     that shows how much mail is each account.
     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 file to control how this program summarizes mail. See 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/ 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";

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.

   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 gets a TV grid/schedule from the web ( 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.

   get_tv_grid [options]
    -h        => This help text
    -help     => This help text
    -userid xyz   => xyz is your clicktv userid.  Go to  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
    get_tv_grid -day 25 -hour 4pm -outfile my_tv.html
    get_tv_grid -days 7 -hour all
    get_tv_grid -email -mail_baseref
    get_tv_grid [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\ -mail_baseref

  Note on finding your provider ID:
   Enter your zip code at
   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 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/ for examples on how to use this from mh.

  Version: 1.14
   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 gets a web page and echoes it to STDOUT or a local file.

    get_url URL [local_file]
    If local_file is specified, data is stored there,
    otherwise it is echoed to STDOUT.
    Options are:
     -quiet: no output on stdout
     -cookies 'cookiestr': Uses the specified cookie string for the request.
      The format of 'cookiestr' should be like this: 'name1=val1; name2=val2'.
     -cookie_file_in  'file':  Like the -cookies option, but string is sent via a file.
     -cookie_file_out 'file':  Generates a cookies string from the resulting web request.
     -userid 'userid'    :  Use these 2 parms if your web page is protected by
     -password 'password':  'basic credentials' (i.e. you get a browser popup).
     -post 'poststr': Makes this a post request with the specified name/value pairs
      as the form data, such as: 'name1=val1&name2=val2'
     -header 'header_file': HTTP headers from the server are stored in this file

Examples: get_url $f_top10_html;


get_weather gets weather info NOAA web servers.

    get_weather [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;

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 item defined that matches that data (e.g. X10 A1 in the above example).


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
    ical_load [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/ ical_load -calendar_file /home/dbl/.calendar -date 11/07/00 -days 2 ical_load -calendar_file /home/dbl/mh/data/calendar ical_load


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 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.

    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 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.

    outlook_read [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_read


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


  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 '' /var/log/httpd/access_log report_weblog -mailto -mailserver e:/mh/data/logs/server.1999_07.log


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 sends data to the MisterHouse program, through the internet/intranet.
  The following flags control what the companion mh 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.
    mhsend 'hi there'
    mhsend -display 60 hi there Bruce
    mhsend -port 8083 -host 'hi there'
    mhsend -speak file_to_speak.txt
    mhsend -file file1 file_to_send.txt
    mhsend -log This is a good URL:
    echo 'hi there' | mhsend -stdin

monitor_weblog monitors a Apache server log file. When the server logs hits, this code will summarize them and pass them onto the MisterHouse code via a tcp/ip socket.


  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,' /var/log/httpd/access_log


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.

    set_clock [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.
    -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:

Example: set_clock set_clock -server

  More info about NIST clock servers if available at:

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 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 item method, or by adding it to your web/bin file # Authority=user comment.

The crypt function limits the password to 8 characters. If -user is not specified, it defaults to family.

    set_password [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


    set_password  -user admin
    set_password  -user family -password xyz

speak will pass the file or text to mh. It simply runs the house program.


    speak "Boo, did I scare you?"


A one way crypt-ed password authentication scheme can be enabled for the web and 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 and web interface will be enable for anyone and everyone. If it does exist, and the correct password is not entered, the and web function will not implement the requested commands.

The set_password command is used to create and check this password.

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 phone:   http://localhost/sub?menu_vxml
  LCD keypads:        see mh/code/bruce/ for an example
  Audible feedback:   see mh/code/public/audible_menu.* for an example

To enable, use mh/code/test/ 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
    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 file in your code directory. This file is re-generated on reload, so copy and edit this file, then point at your edited copy.

You can have different sets of menus, for use with different interfaces or users. For example, the default looks like this:

    my $menu_mh   = menu_create "$config_parms{code_dir}/";
    my $menu_test = file_read   "$config_parms{code_dir}/";
    menu_parse $menu_test, 'default';
    menu_parse $menu_mh,   'mh'; # This menu has all the mh voice commands

menu_create creates the example 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, you can point your WAP phone to:

  Either of these will get the 'default' test menu:
  This will get the auto-generated 'mh' entry for all voice commands:

Something like this could be used to create different menus for different LCD controllers:

  $menu_standard = file_read '';
  $menu_bedroom  = file_read '';
  $menu_living   = file_read '';
  $menu_all      = file_read '';
  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 ( ) (e.g. crystalfontz or matrix-orbital), you can use mh/code/bruce/ 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

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 or download a phone simulator from . Other browsers are listed at , although I didn't have much luck with or / .

Customizing the Tk Interface

You can modify what the Tk window displays by modifying the and members. controls the general layout and size of the various scrolled lists. Modifying this member takes a little knowledge of perl Tk. 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

Any change to 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 member. See the,, and 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 code to control this. Position=1 is reserved for the 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

If you want to just update parts of the default web interface, use this mh.ini parm to point to your own dir:

html_alias2_ia5 = /misterhouse/web/ia5

Then override just the members from the default mh/web/ia5 directories that you want to change. For example, to override the security menu, copy mh/web/ia5/security/menu.html to /misterhouse/web/ia5/security and modify. The other files will still be picked up from the default distro dirs.

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 item state. Icons will be searched for in the mh/graphics directory according to the item icon method, the item name, the item state, and the item type. For example, given this code:

    $fountain = new X10_Appliance('C3');
    set_icon $fountain 'water';

The search order for the ON state will be:


The web Category display will try to match icons to Voice_Cmd items using the icon method, item name, voice command text, and filename. For example, given this code from the member

 $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

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 , , , and

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:

    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
    This can be used to list just the commands in the specified Category.
    As above, but it also passes h_response to the RUN commands (see h_response below)
    This will return all the Group Items
    This will return all the items in $group_name
    This will return all the non Voice items (e.g. X10_Items)
    This will return all the items of type item_type
    This lists all the Tk widgets.
    This returns just the label widgets
    This returns just the entry widgets
    This returns just the radiobutton widgets
    This returns just the checkbox widgets
    This lists the most recently spoken text
    This lists the most recent print_log text
    This lists the most recent error_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/

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:

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/"-->

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"-->


  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:


One example of a built in function is html_item, which will return html for one item. 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 item 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:

  $var and $item are the variable and item 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):
   <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 item is optional):
  <a HREF=";Referer?camera_light=on">Light on</a>
 This will redirect the browser to mh page ia5/lights/main.shtml page:
  <a HREF=";referer/ia5/lights/main.shtml?$camera_light=on">Light on</a>
 This will redirect the browser to

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:;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

For vr with ViaVoice, use mh/code/bruce/

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

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 item names must match those on in your real code on your main mh box. One way to do this is to share the same 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 =,

If you want to distribute speech to mh_proxy machines, see mh/code/public/ for example code.


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:

Another handy utility for Windows users will help you monitor how much CPU and memory mh takes. TaskInfo80, available at

If you are getting the message "Out of environment space" try adding these records:

  config.sys: /e:8000 /p
  autoexec.bat: set comspec=c:\

By default, declaration code like '$item = 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 item
  State_change   => Run when items 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";

To monitor text sent to print_log, display, play, and speak, try this:

  &Log_add_hook(\&my_log) if $Reload;
  sub my_log {
      my ($type)  = shift @_;
      my ($text)  = shift @_;
      my (%parms) = @_;
      print "type=$type text=$text\n";

For more examples on code hooks, see mh/code/examples/


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:,4,33117,00.html? .

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:

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.


Bruce Winter:

and a bunch of fellow home hackers


mh can be download from

Here is a list of articles written about MisterHouse:

You can subscribe and/or view the mailing list at You can view the archive at and search it at you can also view and search the mailing list archive at:

You can post to the mailing list using nabble as well, however you will need to subscribe to the mailing list first, before any messages from nabble will be accepted on the list.

Jan Dubois has a fun page of other ticks you can do with perl on windows at

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 . 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


Copyright (C) 1998-2005 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.


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).