PageRenderTime 63ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/tags/v2-24/mh/bin/mh

#
Perl | 1640 lines | 1350 code | 149 blank | 141 comment | 133 complexity | c04a655b7857db7c5227875d6dd3040a MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.0, GPL-3.0

Large files files are truncated, but you can click here to view the full file

  1. #!/usr/bin/perl
  2. # -*- Perl -*-
  3. # Last change Time-stamp: <2000-08-06 16:48:08 winter>
  4. #---------------------------------------------------------------------------
  5. # File:
  6. # mh
  7. # Description:
  8. # A perl script that does home control functions
  9. # Author:
  10. # Bruce Winter bruce@misterhouse.net
  11. # Latest version:
  12. # http://misterhouse.net
  13. #
  14. # Change log:
  15. # - 03/07/98 Created from house_menu.
  16. # - The rest of the change log is at the bottom of this file.
  17. #
  18. # Documentation is in mh/docs/mh.html (from mh.pod) and mh/docs/install.html
  19. #
  20. # This free software is licensed under the terms of the GNU public license.
  21. # Copyright 1998-2000 Bruce Winter
  22. #
  23. #---------------------------------------------------------------------------
  24. use strict;
  25. my ($Pgm_Name, $Revision, $usage);
  26. # So we can get at it from other packages
  27. use vars qw(%config_parms %config_parms_startup $Pgm_Path $Version $Version_date);
  28. BEGIN {
  29. ($Version) = q$Revision: 100 $ =~ /: (\S+)/; # Auto-updated by CVS
  30. $0 = $ENV{sourceExe} if $ENV{sourceExe}; # perl2exe fills this in
  31. ($Pgm_Path, $Pgm_Name) = $0 =~ /^(.*)[\\\/]([^.]+)/i;
  32. ($Pgm_Name) = $0 =~ /([^.]+)/i unless $Pgm_Name;
  33. $Pgm_Name = 'mh' if $ENV{sourceExe}; # Since we sometimes rename mh.exe
  34. unless ($Pgm_Path) {
  35. use Cwd;
  36. $Pgm_Path = cwd();
  37. # When we do system calls in Dos, we need \, not /
  38. $Pgm_Path =~ tr!\/!\\! if $^O eq "MSWin32";
  39. }
  40. $usage =<<eof;
  41. Description:
  42. $Pgm_Name is a perl program for time, event, web, and voice based home control
  43. functions. Configuration is controled in the \\mh\\bin\\$Pgm_Name.ini file.
  44. See the \\mh\\docs\\$Pgm_Name.html for more info.
  45. Usage:
  46. $Pgm_Name [options] [files]
  47. Where options can be any of the parms listed in the \\mh\\bin\\$Pgm_Name.ini file
  48. Examples usage:
  49. $Pgm_Name
  50. $Pgm_Name -help
  51. $Pgm_Name -tk 0 -code_dir c:\\mh\\code\\test
  52. $Pgm_Name -log_file test1.log -debug 1 test1.pl
  53. eof
  54. }
  55. # Use var instead of my so we can get these in the http_server.pl scripts
  56. use vars qw($Time_Start_time $Time_Stop_time $Time_Increment $Time_Startup $Time_Startup_time $Time_Boot_time $Time_Uptime_Seconds);
  57. use vars qw($Time_Sunrise $Time_Sunset %Moon $Time_Now $Time_Date $Date_Now $Date_Now_Speakable $Year_Month_Now);
  58. use vars qw($Time $Second $Minute $Hour $Mday $Wday $Day $Month $Year);
  59. use vars qw($New_Second $New_Minute $New_Hour $New_Day $New_Week $New_Month $New_Year);
  60. use vars qw($Season $Weekday $Weekend $Holiday $Time_Of_Day);
  61. use vars qw($Startup $Reload $Reread $Loop_Count $Last_Response $Category);
  62. use vars qw($Version_tk);
  63. my ($Pgm_PathU);
  64. my (@Loop_Speeds, $Loop_Sleep_Time, $Loop_Tk_Passes);
  65. my ($Loop_Speed, $Loop_Speed2, $Loop_Speed3);
  66. my (@Requested_Files, @Print_Log, @Display_Log, @Speak_Log);
  67. my ($exit_flag, $xcmd_file, %file_code_times, %file_code_times2, %file_change_times);
  68. my (%User_Code, @Loop_Code, @Sub_Code, %Run_Members, @Item_Code, @Item_Code_Objects);
  69. my ($user_code, $user_code_last_good, $restore_state);
  70. my (%objects_by_object_name, %file_by_object_name, %files_by_webname);
  71. my (%object_names_by_file, %object_names_by_type, %object_names_by_webname);
  72. my (%prev_serial_event, @Generic_Serial_Ports, @Server_Ports, , %Local_Addresses, @Local_Addresses, @Password_Allow_Clients);
  73. my ($CON_IN, $CON_OUT);
  74. #use vars '$CON_IN', '$CON_OUT';
  75. my($loop_tickcount_wtk, $loop_tickcount_wotk, $loop_tickcount_passes);
  76. my($state, $temp); # Some generic useful vars
  77. use vars '%Tk_objects', '%Tk_results', '@Tk_widgets', '@Object_Types'; # So we can use in http_server
  78. use vars '$MW'; # So that programs that we 'do' can use the top window
  79. use vars '%Serial_Ports'; # So we can get at it from the Serial_Item package.
  80. use vars '%Socket_Ports';
  81. use vars '%Save';
  82. use vars '%Info', '$OS_win';
  83. use vars '%Password_Allow'; # So we can see check it from http_server.pl
  84. use vars '$Pgm_Root'; # So we can see it in eval var subs in read_parms
  85. use vars '$DNS_resolver';
  86. # Pre-declare these so we don't fail on non-windows platforms
  87. sub Win32::GetOSVersion;
  88. sub Win32::FsType;
  89. sub Win32::GetCwd;
  90. sub Win32::LoginName;
  91. sub Win32::NodeName;
  92. sub Win32::IsWinNT;
  93. sub Win32::IsWin95;
  94. sub Win32::GetTickCount;
  95. sub Win32::DriveInfo::DrivesInUse;
  96. &setup;
  97. &read_code; # Load all menus
  98. &monitor_commands;
  99. BEGIN {
  100. &check_for_run_cmd;
  101. &print_version;
  102. &check_usage;
  103. &setup_INC;
  104. &read_parms;
  105. &use_conditional_modules;
  106. print "Loading other modules\n";
  107. sub check_for_run_cmd {
  108. # This lets us use mh as a perl interpreter for running arbitrary perl code
  109. if ($ARGV[0] and $ARGV[0] eq '-run') {
  110. # @ARGV = split(/[, ]/, $config_parms{run_parms});
  111. shift @ARGV;
  112. my $pgm = shift @ARGV;
  113. my $pgm_path = $pgm;
  114. # Best to change to the mh bin dir, so their Paths are correct.
  115. # $pgm_path = "$Pgm_Path/$pgm" unless -e $pgm_path;
  116. chdir $Pgm_Path unless -e $pgm_path;
  117. unless (-e $pgm_path) {
  118. print "\nCan not find -run pgm: $pgm\n\n";
  119. exit;
  120. }
  121. print "\nRunning: $pgm_path @ARGV\n";
  122. $0 = $pgm; # Reset program name from mh to $pgm
  123. do "$pgm_path";
  124. print "Error with $pgm: $@\n" if $@;
  125. print "\nDone running: $pgm\n";
  126. exit;
  127. }
  128. }
  129. sub print_version {
  130. $Version_date= localtime((stat $0)[9]);
  131. # perl2exe sets this var
  132. if ($ENV{sourceExe}) {
  133. $Version_date= localtime((stat $ENV{sourceExe})[9]);
  134. $Version .= " (compiled)" unless $Version =~ /compiled/;
  135. }
  136. ($Pgm_PathU = $Pgm_Path) =~ tr/\\/\//;
  137. $Pgm_Root = "$Pgm_PathU/..";
  138. $OS_win = ($^O eq "MSWin32") ? 1 : 0;
  139. # Win95: MSWin32 Win95 B 4 0 67306684 1 FAT32
  140. # Win98: MSWin32 Win95 4 10 67766222 1 FAT
  141. if ($OS_win) {
  142. $Info{OS_version} = join(' ', Win32::GetOSVersion);
  143. $Info{OS_name} = 'NT' if Win32::IsWinNT;
  144. $Info{OS_name} = 'Win95' if Win32::IsWin95;
  145. $Info{OS_filesystem} = Win32::FsType;
  146. $Info{User} = Win32::LoginName;
  147. $Info{Machine} = Win32::NodeName;
  148. }
  149. else {
  150. $Info{OS_name} = $^O;
  151. $Info{User} = $ENV{USER};
  152. $Info{Machine} = $ENV{HOSTNAME};
  153. }
  154. print "\nCommand: $Pgm_Name @ARGV\n";
  155. print "Pgm path : $Pgm_Path\n";
  156. print "Pgm version: $Version Last updated: $Version_date\n";
  157. $Info{Perl_version} = $];
  158. # BuildNumber doesn't work with perl2exe compile :(
  159. $Info{Perl_version} .= " Build " . &Win32::BuildNumber() if $OS_win and !$ENV{sourceExe};
  160. print "Perl version: $Info{Perl_version}\n";
  161. print "OS version: $^O $Info{OS_name} $Info{OS_version} $Info{OS_filesystem}\n";
  162. print "Other : user=$Info{User} pid=$$ box=$Info{Machine} cpu=$ENV{PROCESSOR_ARCHITECTURE}-$ENV{PROCESSOR_LEVEL}\n";
  163. print "\n";
  164. }
  165. sub check_usage {
  166. # Get legal options from .ini file
  167. # my $parmfile = $Pgm_PathU . "/$Pgm_Name.ini";
  168. my $parmfile = $Pgm_PathU . "/mh.ini";
  169. open (PARMS, $parmfile) or die "Error, could not open parmfile $parmfile: $!\n";
  170. my @parms;
  171. while (<PARMS>) {
  172. push(@parms, "$1=s") if /^([^\s\#]+) *=/;
  173. }
  174. close PARMS;
  175. # print "db parms=@parms\n";
  176. use Getopt::Long;
  177. if (!&GetOptions(\%config_parms_startup, "h", "help", "run=s", "run_parms=s", @parms) or
  178. ($config_parms_startup{h} or $config_parms_startup{help})) {
  179. print $usage;
  180. exit;
  181. }
  182. }
  183. sub setup_INC {
  184. print "Setting up INC path ...";
  185. # Note, use lib messes up perl2exe, but eval use lib does not. Either use
  186. # eval use lib (only once, because it is slow in perl2exe) or comment out use lib
  187. # when compiling and use perl5lib env. Yuck.
  188. # - can not seem to get perl2exe to honor this. Guess we have to make sure
  189. # everything is compile by adding it to lib/mh_perl2exe_list.pl
  190. # eval "use lib '$Pgm_PathU/../lib', '$Pgm_PathU/../lib/site', '$Pgm_PathU', '../lib', '../lib/site', '.'";
  191. # eval "use lib '$Pgm_PathU/../lib', '$Pgm_PathU/../lib/site', '$Pgm_PathU'";
  192. # Use a push instead of a use lib, so we make sure the site perl libs come first, not last
  193. # - We need to support perl 5.005 and 5.6 at the same time. 5.005 win32 binaries
  194. # are distributed with mh (so user does not have to install them).
  195. # 5.6 users will be required to install them
  196. push (@INC, "$Pgm_PathU/../lib", "$Pgm_PathU/../lib/site", "$Pgm_PathU");
  197. # print "Error in use lib: $@\n" if $@; # Dang .. this error check messes up the compiled version!?? Gives 'can not find lib.pm' message
  198. print " done\n";
  199. require 'handy_utilities.pl'; # For misc. functions (e.g. time/date stamp routines)
  200. }
  201. sub read_parms {
  202. my $debug = 1 if $config_parms_startup{debug} and $config_parms_startup{debug} eq 'startup';
  203. &main::read_mh_opts(\%config_parms, $Pgm_PathU, $debug);
  204. # We need to honor starup parms, but a total reset messes up the tk debug button interface
  205. # %config_parms = (%config_parms, %config_parms_startup); # Last one (startup parms) wins
  206. for my $parm (keys %config_parms_startup) {
  207. $config_parms{$parm} = $config_parms_startup{$parm};
  208. }
  209. print "Code Directory: $config_parms{code_dir}\n";
  210. }
  211. sub use_conditional_modules {
  212. if ($config_parms{diagnostics} or $config_parms{w}) {
  213. print "Perl diaganotics module (perl -w) has been turned on\n";
  214. eval 'use diagnostics' if $config_parms{diagnostics};
  215. }
  216. # disable diagnostics;
  217. # This must be in a BEGIN in order for the 'use' to be conditional
  218. if ($OS_win) {
  219. print "Loading Windows modules\n";
  220. # Must use 'my_use' (evals) so unix doesn't croak on missing modules
  221. # Not sure what we gain/loose with ole lite
  222. # &my_use("Win32::DUN"); # Interface to rasdial
  223. &my_use("Win32::DriveInfo"); # For disk space free/total
  224. eval "use Win32::Console";
  225. print "\nError in loading module=Win32::Console:\n $@\n" if $@;
  226. &my_use("Win32::OLE");
  227. # &my_use("Win32::OLE qw(EVENTS)"); # EVENTS forces single-threaded apartment, so outlook.pl MAPI works
  228. # &my_use("Win32::OLE::lite");
  229. &my_use("Win32::Process");
  230. &my_use("Win32::Registry");
  231. # Note: Must use eval, not my_use, or exported pgms are not seen :(
  232. # - setupsup is not yet available for perl 5.6
  233. # - BuildNumber doesn't work with perl2exe compile
  234. # - Loaded from: http://jenda.krynicky.cz/perl/
  235. eval "use Win32::Setupsup qw(WaitForAnyWindow SendKeys)" if $ENV{sourceExe} or &Win32::BuildNumber() < 600;
  236. print "\nError in loading module=Win32::Setupsup:\n $@\n" if $@;
  237. eval "use Win32::Sound";
  238. print "\nError in loading module=Win32::Sound:\n $@\n" if $@;
  239. &my_use("Win32::SerialPort");
  240. &my_use("Win32::SoundEx"); # For Volume control
  241. &my_use("File::DosGlob 'glob'"); # Allow for globbing without perlglob.exe
  242. }
  243. else { # load the mutually-exclusive non-Windows modules
  244. &my_use("Device::SerialPort"); # Unix Posix verion of Win32 SerialPort
  245. }
  246. if ($config_parms{tk}) {
  247. print "Loading Tk modules ";
  248. eval "use Tk";
  249. # eval "use Tk qw/DoOneEvent DONT_WAIT ALL_EVENTS/";
  250. $Version_tk = $Tk::VERSION;
  251. print "Version $Tk::VERSION\n";
  252. if ($@) {
  253. print "\nError, perl Tk module is not installed.\nTk windows will be disabled with the -tk 0 option. Error:$@\n\n";
  254. $config_parms{tk} = 0;
  255. }
  256. else {
  257. &my_use("Display");
  258. }
  259. }
  260. }
  261. } # End BEGIN
  262. sub setup {
  263. print "Starting setup\n";
  264. $| = 1; # Turn on command buffering (e.g. flush on every print)
  265. my $logfile = $config_parms{log_file};
  266. $config_parms{log}++ if $logfile;
  267. $config_parms{log_file} = $Pgm_PathU . "/../mh.log" unless $config_parms{log_file};
  268. my $print_log = $Pgm_PathU . "/../mh.print.log";
  269. rename $print_log, $print_log . ".old";
  270. open PRINTLOG, ">$print_log" or die "Error, could not open print lotfile $print_log: $!\n";
  271. if ($config_parms{log}) {
  272. print "Output will be logged into $logfile\n";
  273. open STDOUT, ">$logfile" or die "Error, could not open logfile $logfile: $!\n";
  274. &print_version;
  275. }
  276. use File::Copy; # So we copy files
  277. # use FreezeThaw qw(freeze thaw cmpStr safeFreeze cmpStrHard);
  278. # use Date::Parse; # For str2time
  279. use Time::Local; # For timelocal
  280. use LWP::Simple; # For pgms like set_clock that need to grab data from urls
  281. use Net::FTP; # For uploading stuff
  282. if ($config_parms{DNS_server}) {
  283. print "Loading DNS code ...";
  284. &my_use("Net::DNS::Resolver"); # for doing reverse DNS search
  285. $DNS_resolver = new Net::DNS::Resolver;
  286. $DNS_resolver->nameservers(split(',', $config_parms{DNS_server}));
  287. print " DNS set to $config_parms{DNS_server}\n";
  288. }
  289. use IO::Socket;
  290. &my_use("DB_File"); # Need by get_tv_grid
  291. use Fcntl; # To enable O_RDWR|O_CREAT
  292. # use MIME::Base64; # Needed for uudecode/uuencode in http_server and mhsend_server
  293. use Timer; # This needs to be first, as it is used in Voice_Cmd (and elsewhere?)
  294. use File_Item;
  295. use Generic_Item;
  296. use Group;
  297. use IR_Item;
  298. # &my_use("Serial_Item"); # So we can add debug to Serial_Item.pm when running mh.exe
  299. use Serial_Item;
  300. use X10_Items;
  301. use iButton;
  302. use Hardware::iButton::Connection;
  303. use Socket_Item;
  304. use Process_Item;
  305. use Voice_Cmd;
  306. use Voice_Text;
  307. use Caller_ID;
  308. use Astro::MoonPhase;
  309. use Text::Wrap;
  310. use constant; # To keep perl2exe happy
  311. use constant ON => 'on';
  312. use constant OFF => 'off';
  313. use constant STATUS => 'status';
  314. use constant OPEN => 'open';
  315. use constant CLOSE => 'close';
  316. use constant OPENED => 'opened';
  317. use constant CLOSED => 'closed';
  318. require 'handy_net_utilities.pl'; # For misc. net functions (e.g. net_mail_read)
  319. require 'console_utils.pl';
  320. require 'http_server.pl';
  321. if ($OS_win) {
  322. # These are the modules that perl2exe can not find on its own
  323. # Note: tk windows starts faster in mh.exe if we run this,
  324. # even though they are included with the perl2exe_include!
  325. # require 'mh_perl2exe_list.pl';
  326. #perl2exe_include mh_perl2exe_list.pl
  327. no strict 'subs'; # For non-win OS
  328. if (1) {
  329. $CON_IN = new Win32::Console STD_INPUT_HANDLE;
  330. $CON_OUT= new Win32::Console STD_OUTPUT_HANDLE;
  331. $CON_OUT->Title("Mister House");
  332. # use vars '$FG_WHITE', '$BG_CYAN';
  333. # &explodeAttr($CON_OUT, $FG_WHITE | $BG_CYAN);
  334. # $CON_OUT->Attr($FG_WHITE | $BG_CYAN);
  335. }
  336. }
  337. $SIG{INT} = \&sig_handler; # Exit cleanly with CTL-C
  338. $SIG{BREAK} = \&sig_handler if $OS_win; # Exit cleanly with BREAK
  339. $SIG{KILL} = \&sig_handler; # Exit cleanly with a kill signal
  340. $SIG{HUP} = \&read_code if !$OS_win; # Reload code (alias mhreload per info in mh.ini file)
  341. $SIG{PIPE} = "IGNORE"; # Web browsers can shut down sockets while we are sending data
  342. $config_parms{code_dir} = $Pgm_PathU . "/../code" unless $config_parms{code_dir};
  343. # Make various directories, if missing
  344. mkdir ("$config_parms{data_dir}/logs", 0777) unless -d "$config_parms{data_dir}/logs";
  345. mkdir ("$config_parms{data_dir}/web", 0777) unless -d "$config_parms{data_dir}/web";
  346. mkdir ("$config_parms{html_dir}/tv", 0777) unless -d "$config_parms{html_dir}/tv";
  347. mkdir ("$config_parms{html_dir}/tv/clicktv", 0777) unless -d "$config_parms{html_dir}/tv/clicktv";
  348. open(ERROR_LOG, ">>$config_parms{code_dir}/mh_temp.error_log") or
  349. print "Error, could not open error log $config_parms{code_dir}/-error_log-: $!\n";
  350. &add_hook_code;
  351. # print "parms=", join(":", %config_parms), "\n";
  352. if ($config_parms{voice_cmd}) {
  353. &Voice_Cmd::init;
  354. }
  355. if ($config_parms{voice_text}) {
  356. &Voice_Text::init;
  357. }
  358. # Find all defined socket and serial ports
  359. for my $parm (keys %config_parms) {
  360. next unless $config_parms{$parm}; # Ingore blank parms
  361. push(@Server_Ports, $1) if $parm =~ /(http)_port/;
  362. push(@Server_Ports, $1) if $parm =~ /(server\S+)_port/;
  363. push(@Generic_Serial_Ports, $1) if $parm =~ /(serial\S+)_port/;
  364. }
  365. # print "Server ports defined: @Server_Ports\n" if @Server_Ports;
  366. # print "Generic serial ports defined: @Generic_Serial_Ports\n" if @Generic_Serial_Ports;
  367. # print "Creating socket server ports: @Server_Ports\n" if @Server_Ports;
  368. print "Creating socket and serial objects\n";
  369. for my $port_name (@Server_Ports) {
  370. my $port = $config_parms{$port_name . "_port"};
  371. my $proto = $config_parms{$port_name . "_protocol"};
  372. my $datatype = $config_parms{$port_name . "_datatype"};
  373. $proto = 'tcp' unless $proto;
  374. $datatype = 'buffered' if $port_name eq 'http';
  375. $datatype = 'buffer' if $config_parms{$port_name . "_buffer"}; # Grandfathered syntax
  376. printf " - creating %-15s on %5s %s\n", $port_name, $proto, $port;
  377. $Socket_Ports{$port_name}{protocol} = $proto;
  378. $Socket_Ports{$port_name}{datatype} = $datatype;
  379. if ($proto eq 'tcp') {
  380. $Socket_Ports{$port_name}{sock} = new IO::Socket::INET->new(LocalPort => $port, Proto => 'tcp', Reuse => 1, Listen => 10) or
  381. die "Couldn't start a tcp server on $port_name $port: $@\nTo get mh to run, blank out or change the ${port}_port in mh.ini\n";
  382. } elsif ($proto eq 'udp') {
  383. $Socket_Ports{$port_name}{sock} = new IO::Socket::INET->new(LocalPort => $port, Proto => 'udp') or
  384. die "Couldn't start a udp server on $port_name $port: $@\n";
  385. $Socket_Ports{$port_name}{socka} = $Socket_Ports{$port_name}{sock}; # UDP ports are always "active"
  386. } else {
  387. die "Unknown protocol for $port_name \n";
  388. }
  389. }
  390. for my $port_name (@Generic_Serial_Ports) {
  391. &serial_port_create($port_name, $config_parms{$port_name . "_port"},
  392. $config_parms{$port_name . "_baudrate"},
  393. $config_parms{$port_name . "_handshake"},
  394. $config_parms{$port_name . "_datatype"});
  395. }
  396. # Create managed serial and server ports
  397. # This makes it easy to add new modules
  398. # for new serial/socket devices.
  399. # Manager must be available in lib directory
  400. # (e.g. Compool.pm)
  401. for my $parm (keys %config_parms) {
  402. next unless $config_parms{$parm};
  403. if ($parm =~ /^(\S+)_(serial|server)_port/) {
  404. if (-e "$Pgm_PathU/../lib/$1.pm") {
  405. print "Found managed $2 port=$1\nMH will now require $1.pm and call $1::$2_startup($parm)\n"
  406. if $config_parms{debug} eq 'startup';
  407. require "$1.pm";
  408. eval "&$1::$2_startup('$parm')";
  409. print "Startup errror on &$1::$2_startup('$parm'): $@\n" if $@;
  410. }
  411. else {
  412. print "No $1.pm file found for $parm\n";
  413. }
  414. }
  415. }
  416. if ($config_parms{weeder_port}) {
  417. &serial_port_create('weeder', $config_parms{weeder_port}, 1200, 'dtr');
  418. $Serial_Ports{weeder}{process_data} = 1;
  419. }
  420. if ($config_parms{cm11_port}) {
  421. require 'ControlX10/CM11.pm';
  422. &serial_port_create('cm11', $config_parms{cm11_port}, 4800, 'none');
  423. }
  424. if ($config_parms{Homevision_port}) {
  425. require 'Homevision.pm';
  426. my($speed) = $config_parms{Homevision_baudrate} || 9600;
  427. if (&serial_port_create('Homevision', $config_parms{Homevision_port}, $speed, 'none')) {
  428. &Homevision::init($Serial_Ports{Homevision}{object}); # Turn on Echo mode
  429. }
  430. }
  431. if ($config_parms{Marrick_port}) {
  432. require 'Marrick.pm';
  433. my($speed) = $config_parms{Marrick_baudrate} || 9600;
  434. if (&serial_port_create('Marrick', $config_parms{Marrick_port}, $speed, 'none')) {
  435. &Marrick::init($Serial_Ports{Marrick}{object});
  436. }
  437. }
  438. if ($config_parms{HomeBase_port}) {
  439. require 'HomeBase.pm';
  440. my($speed) = $config_parms{HomeBase_baudrate} || 9600;
  441. if (&serial_port_create('HomeBase', $config_parms{HomeBase_port}, $speed, 'none')) {
  442. &HomeBase::init($Serial_Ports{HomeBase}{object}); # Turn on Echo mode
  443. }
  444. }
  445. if ($config_parms{ibutton_port}) {
  446. &my_use("Hardware::iButton::Connection;");
  447. &iButton::connect($config_parms{ibutton_port});
  448. }
  449. # Do this one last, as it can share a serial port.
  450. if ($config_parms{cm17_port}) {
  451. require 'ControlX10/CM17.pm';
  452. &serial_port_create('cm17', $config_parms{cm17_port});
  453. }
  454. if($config_parms{weather_sblog_file} or
  455. $config_parms{weather_vwlog_file} or
  456. $config_parms{serial_wx200}) {
  457. require 'Weather.pm';
  458. &Weather_Data::Init();
  459. }
  460. if ($OS_win) {
  461. $Time_Boot_time = 0; # Gettickcount starts at computer boot
  462. }
  463. elsif ($^O eq 'linux') {
  464. # Linux output:
  465. # uptime: 2 hours 10:38pm up 2:10, 6 users, load average: 0.83, 0.45, 0.18
  466. # /proc/stat: cpu 10339 0
  467. # /proc/pid/stat: 732 (ghx2) S 565 732 376 1025 561 256 529 0 1194 0 142 35 0 0 0 0 0 0 499881 6688768 905 2147483647 134512640 134754096 3221224640 3221223956 1075917534 0 0 69632 17479 3222448608 0 0 17
  468. # Not sure if 1st number
  469. open(UPTIME, "/proc/uptime") or print "\nError: can't open /proc/uptime ($!)\n";
  470. my ($uptime, $idletime) = (<UPTIME> =~ /(\S+) (\S+)/);
  471. close UPTIME;
  472. $Time_Boot_time = time - $uptime;
  473. }
  474. $exit_flag = 0;
  475. # $Loop_Sleep_Time = 0;
  476. # $Loop_Sleep_Time = 20;
  477. $config_parms{sleep_time} = 50 unless defined $config_parms{sleep_time};
  478. $Loop_Sleep_Time = $config_parms{sleep_time};
  479. $config_parms{tk_passes} = 10 unless $config_parms{tk_passes};
  480. $config_parms{tk_font} = 'Times 10 bold' unless $config_parms{tk_font};
  481. $config_parms{tk_font_fixed} = 'Courier 10 bold' unless $config_parms{tk_font_fixed};
  482. $Loop_Tk_Passes = $config_parms{tk_passes};
  483. $Time = time;
  484. ($Second, $Minute, $Hour, $Mday, $Month, $Year) = localtime $Time; # Needed in my_str2time;
  485. $Time_Date = &time_date_stamp(12, $Time); # Needed by print_log
  486. $Month++;
  487. # Configure 'fast test mode' parms
  488. $Time_Increment = ($config_parms{time_increment}) ? $config_parms{time_increment} : 60;
  489. if ($config_parms{time_start} =~ /\S/) {
  490. $Loop_Sleep_Time = 0;
  491. $Loop_Tk_Passes = 1;
  492. $Time_Start_time = &my_str2time($config_parms{time_start});
  493. $Time = $Time_Start_time - $Time_Increment; # Cause we start the loop with an increment
  494. print "time_start=$config_parms{time_start} -> $Time_Start_time \n";
  495. }
  496. if ($config_parms{time_stop} =~ /\S/) {
  497. $Loop_Sleep_Time = 0;
  498. $Loop_Tk_Passes = 1;
  499. $Time_Stop_time = &my_str2time($config_parms{time_stop});
  500. $Time_Stop_time += 3600*24 if $Time_Stop_time < $Time_Start_time;
  501. print "time_stop =$config_parms{time_stop} -> $Time_Stop_time \n";
  502. }
  503. $Time_Startup_time = $Time;
  504. $Time_Startup = &time_date_stamp(9, $Time_Startup_time);
  505. $Startup = 1;
  506. if ($config_parms{pid_file}) {
  507. print "Process id $$ written to $config_parms{pid_file}\n";
  508. &file_write($config_parms{pid_file}, $$);
  509. }
  510. if ($config_parms{tk}) {
  511. &tk_setup_windows;
  512. }
  513. # $xcmd_file = "$config_parms{temp_dir}/house_cmd.cmd" if $config_parms{xcmd_file};
  514. # print "X command file: $config_parms{xcmd_file}\n" if $config_parms{xcmd_file};
  515. # Use eval to change $ENV{temp} to the real value ... this is done in read_opts now
  516. # eval "\$config_parms{xcmd_file} = qq[$config_parms{xcmd_file}]";
  517. print "External command file (xcmd_file): $config_parms{xcmd_file}\n" if $config_parms{xcmd_file};
  518. $config_parms{html_dir} = $config_parms{html_root} if $config_parms{html_root}; # Grandfather in the old name for this parm
  519. print "HTML file : $config_parms{html_dir}/$config_parms{html_file}\n";
  520. print "\nError, HTML file not found: $config_parms{html_dir}/$config_parms{html_file}\n\n" unless -e "$config_parms{html_dir}/$config_parms{html_file}";
  521. @Requested_Files = @ARGV;
  522. &password_read;
  523. srand(time() ^ ($$ + ($$ << 15)) ); # Set the randum number seed, used in time_random;
  524. $config_parms{max_log_entries} = 50 unless defined $config_parms{max_log_entries};
  525. $config_parms{max_state_log_entries} = 10 unless defined $config_parms{max_state_log_entries};
  526. print "Done with setup\n\n";
  527. }
  528. # The remaining subroutines are in alphabetical order
  529. # This code allows us to add dynamic user code hooks at various places.
  530. my (%hook_pointers, %hook_pointers_persistent, %hook_locations);
  531. sub add_hook_code {
  532. %hook_locations = ( MainLoop_pre => 1, MainLoop_post => 1 );
  533. for my $location (keys %hook_locations){
  534. my($accessors) = "
  535. sub ${location}_add_hook { return add_hook_ ( '$location', \@_ ) }
  536. sub ${location}_drop_hook { return drop_hook_( '$location', \@_ ) }
  537. sub ${location}_get_hooks { return get_hooks_( '$location' ) }
  538. sub ${location}_hooks { return run_hooks_( '$location', \@_ ) }
  539. ";
  540. eval $accessors;
  541. die "Eval error $@\n" if $@;
  542. }
  543. sub add_hook_ {
  544. my($location, $hook, $persistent) = @_;
  545. unless( defined( $hook_locations{$location} ) ){
  546. warn "Invalid hook location $location\n";
  547. return 0;
  548. }
  549. unless( ref $hook eq 'CODE' ){
  550. warn "Hook must be a code reference\n";
  551. return 0;
  552. }
  553. $hook_pointers{$location} = [] unless defined ($hook_pointers{$location});
  554. push( @{$hook_pointers{$location}}, $hook );
  555. push( @{$hook_pointers_persistent{$location}}, $hook ) if $persistent;
  556. return 1;
  557. }
  558. sub drop_hook_ {
  559. my($location, $hook ) = @_;
  560. unless( defined( $hook_locations{$location} ) ){
  561. warn "Invalid hook location $location\n";
  562. return 0;
  563. }
  564. if( defined ($hook_pointers{$location}) ){
  565. my($h)=$hook_pointers{$location};
  566. my($i)=-1;
  567. for ( $i=$#{$h}; $i >= 0; $i-- ) {
  568. last if ($hook == $h->[$i] );
  569. }
  570. # delete if the index returned is in range
  571. if ($i >=0 and $i <= $#{$h} ){
  572. splice( @{$h}, $i, 1 );
  573. return 1;
  574. }
  575. }
  576. warn "specified hook not found: $location\n";
  577. return 0;
  578. }
  579. sub get_hooks_ {
  580. my($location) = @_;
  581. return defined $hook_pointers{$location} ? @{$hook_pointers{$location}} : ();
  582. }
  583. # call all hooks with user specified args, if any
  584. sub run_hooks_ {
  585. my($location) = @_;
  586. for my $hook (&get_hooks_($location)){
  587. &$hook(@_)
  588. }
  589. }
  590. # This will keep hook code defined with the persistent
  591. # flag (e.g. module code that is defined on startup).
  592. # All other user code is undefed.
  593. sub reset_hook_code {
  594. for my $location (keys %hook_locations) {
  595. if ($hook_pointers_persistent{$location}) {
  596. @{$hook_pointers{$location}} = @{$hook_pointers_persistent{$location}};
  597. }
  598. else {
  599. delete $hook_pointers{$location};
  600. }
  601. }
  602. }
  603. }
  604. sub browser {
  605. my ($file) = @_;
  606. # Don't need this ... run look at search path
  607. # unless (-f $config_parms{browser} or lc($config_parms{browser}) eq 'explorer') {
  608. # &print_log("Could not find html browser file: $config_parms{browser}");
  609. # return;
  610. # }
  611. # Translate unix/perl / to dos \
  612. $file =~ s|/|\\|g if $OS_win and $file !~ /^http/i;
  613. run "$config_parms{browser} $file";
  614. }
  615. my ($leave_socket_open_passes, $leave_socket_open_action);
  616. sub check_for_action {
  617. my $loop_tickcount1 = (&get_tickcount);
  618. if ($MW) {
  619. $Loop_Tk_Passes = 100 if $Loop_Tk_Passes >100; # Make sure we don't have too many passes
  620. $Loop_Tk_Passes = 1 if $Loop_Tk_Passes < 1; # Make sure we make at least one pass
  621. for (1 .. $Loop_Tk_Passes) {
  622. my $tk_activity;
  623. $tk_activity = DoOneEvent(0xFF); # Avoid Constants ... we get compile errors if -tk 0
  624. # $tk_activity = DoOneEvent(DONT_WAIT | ALL_EVENTS);
  625. # $tk_activity = DoOneEvent(0x1E);
  626. # $tk_activity = DoOneEvent(0x02);
  627. }
  628. # print "tk_activity=$tk_activity\n" if $tk_activity;
  629. }
  630. my $loop_tickcount2 = (&get_tickcount);
  631. &exit_pgm if $exit_flag;
  632. &set_global_vars;
  633. &Process_Item::harvest; # Check for done processes
  634. &Generic_Item::reset_states; # Reset states for all objects that are 'ISA Item' objects
  635. &Voice_Cmd::check_for_voice_cmd; # Do this even if VR is not installed, so we can do web and manual run_voice_cmd
  636. &check_for_keyboard_input;
  637. &check_for_serial_data if %Serial_Ports;
  638. &check_for_socket_data if %Socket_Ports;
  639. &check_for_timer_actions;
  640. &check_for_external_command_file;
  641. &MainLoop_pre_hooks(); # Created by &add_hooks
  642. # Use eval to catch minor errors without abending
  643. # - about 10% slower (170 -> 150)
  644. &eval_user_code_loop;
  645. # &loop_code;
  646. &MainLoop_post_hooks(); # Created by &add_hooks
  647. # Keep track of ticks per pass (average per second)
  648. my $loop_tickcount3 = (&get_tickcount);
  649. $loop_tickcount_wtk += $loop_tickcount3 - $loop_tickcount1;
  650. $loop_tickcount_wotk += $loop_tickcount3 - $loop_tickcount2;
  651. $loop_tickcount_passes++;
  652. if ($New_Second) {
  653. my $loop_tickcount;
  654. $loop_tickcount = $loop_tickcount_wtk / $loop_tickcount_passes;
  655. $Loop_Speed2 = sprintf("%3d", 1000/$loop_tickcount) if $loop_tickcount;
  656. $loop_tickcount = $loop_tickcount_wotk / $loop_tickcount_passes;
  657. $Loop_Speed3 = sprintf("%3d", 1000/$loop_tickcount) if $loop_tickcount;
  658. # print "db $loop_tickcount2 tc=$loop_tickcount $loop_tickcount_passes $loop_tickcount $Loop_Speed2\n";
  659. $loop_tickcount_wtk = $loop_tickcount_wotk = $loop_tickcount_passes = 0;
  660. }
  661. # exit 1 if $number > 6;
  662. }
  663. sub check_for_cm11_data {
  664. my $data = &ControlX10::CM11::read($Serial_Ports{cm11}{object}, 1);
  665. return unless $data;
  666. my $data_d = unpack('C', $data); # Convert from string to decimal
  667. # Check for the official 0x5a=90 string and 0xa5=165 (I have seen this!)
  668. print "mh CM11 data=$data data_d=$data_d\n" if $config_parms{debug} eq 'X10' and $data;
  669. # if ($data_d == 0x5a) {
  670. if ($data_d == 0x5a or $data_d == 0xa5) {
  671. if ($data = &ControlX10::CM11::receive_buffer($Serial_Ports{cm11}{object})) {
  672. # Process status requests
  673. if ($data =~ /STATUS/) {
  674. my ($house, $device, $state) = $data =~ /(\S)(\S)STATUS_(\S+)/;
  675. $state = 'J' if $state eq 'ON';
  676. $state = 'K' if $state eq 'OFF';
  677. my $event_data = 'X' . $house . $device . $house . $state;
  678. if (my @refs = &Serial_Item::serial_items_by_id($event_data)) {
  679. for my $ref (@refs) {
  680. if ($state = $$ref{state_by_id}{$event_data}) {
  681. # set_receive $ref $state;
  682. $ref->{state} = $state;
  683. print "CM11 Status results: data=$data event_data=$event_data state=$state\n"
  684. if $config_parms{debug} eq 'X10';
  685. }
  686. }
  687. }
  688. else {
  689. &print_log("Status request on undefined state: data=$data event_data=$event_data");
  690. }
  691. }
  692. else {
  693. &process_serial_data("X" . $data, 1);
  694. }
  695. }
  696. }
  697. }
  698. sub check_for_external_command_file {
  699. my ($cmd, $cmd_num, $ref, $said);
  700. my $xcmd_file = $config_parms{xcmd_file};
  701. # Checking for a file is pretty slow ...
  702. return unless $New_Second;
  703. # Note: Check for non-zero size, not -e. Zero length files cause a loop!
  704. if ($xcmd_file and -s $xcmd_file) {
  705. &print_log("External command file found: $xcmd_file");
  706. unless (open(XCMD, $xcmd_file)) {
  707. print "\nWarning, can not open file $xcmd_file: $!\n";
  708. return;
  709. }
  710. $cmd = <XCMD>;
  711. chomp($cmd);
  712. close XCMD;
  713. next unless $cmd;
  714. unlink $xcmd_file;
  715. &process_external_command($cmd, 1);
  716. }
  717. }
  718. sub check_for_generic_serial_data {
  719. my ($port_name) = @_;
  720. my $data;
  721. unless ($data = $Serial_Ports{$port_name}{object}->input) {
  722. # If we do not do this, we may get endless error messages.
  723. $Serial_Ports{$port_name}{object}->reset_error;
  724. }
  725. $Serial_Ports{$port_name}{data} .= $data if $data;
  726. print " serial name=$port_name type=$Serial_Ports{$port_name}{datatype} data2=$Serial_Ports{$port_name}{data}...\n"
  727. if $data and ($config_parms{debug} eq 'serial' or $config_parms{debug} eq $port_name);
  728. # Check to see if we have a carrage return yet
  729. if ($Serial_Ports{$port_name}{data} and
  730. (!defined $Serial_Ports{$port_name}{datatype} or $Serial_Ports{$port_name}{datatype} ne 'raw')) {
  731. while (my($record, $remainder) = $Serial_Ports{$port_name}{data} =~ /(.+?)[\r\n]+(.*)/s) {
  732. &print_log("Data from $port_name: $record. remainder=$remainder.") if $config_parms{debug} eq 'serial';
  733. $Serial_Ports{$port_name}{data_record} = $record;
  734. $Serial_Ports{$port_name}{data} = $remainder;
  735. if ($Serial_Ports{$port_name}{process_data}) {
  736. &process_serial_data($record, 1);
  737. }
  738. else {
  739. last; # Only process one data_record per user_code loop
  740. }
  741. }
  742. }
  743. }
  744. sub check_for_Homevision_data {
  745. my $data = &Homevision::read($Serial_Ports{Homevision}{object});
  746. if ($data) {
  747. print "Homevision data=$data\n" if $config_parms{debug} =~ /homevision|serial/i;
  748. &process_serial_data($data, 1);
  749. }
  750. }
  751. sub check_for_HomeBase_data {
  752. my $data = &HomeBase::read($Serial_Ports{HomeBase}{object});
  753. if ($data) {
  754. print "HomeBase x10 data=$data\n" if $config_parms{debug} eq 'homebase';
  755. &process_serial_data("X" . $data, 1);
  756. }
  757. }
  758. sub check_for_keyboard_input {
  759. my $key;
  760. # return; # Console off for now.
  761. # Need to find a way to do this in Linux
  762. return unless $OS_win;
  763. for(0..$CON_IN->GetEvents()-1) {
  764. # Event data: 1, keyup_down, key_repeat_count, id1, id2, id3, id4
  765. # id1 seems to cover all the keys (e.g. 112 is F1, a=65, A=65)
  766. # id2 seems to be keyboard positional (e.g. a=30, s=31)
  767. # id3 seems to be ascii (a=97, A=65)
  768. my @event = $CON_IN->Input();
  769. $key = $event[3] if $event[1];
  770. }
  771. return unless $key;
  772. my %keymap = ('F1: Reload' => 'F1', 'F2: Pause' => 'F2', 'F3: Exit' => 'F3',
  773. 'F4: Debug' => 'F4', 'F5: Logging' => 'F5',
  774. 112 => 'F1', 113 => 'F2', 114 => 'F3',
  775. 115 => 'F4', 116 => 'F5');
  776. if ($key == 13) { # Enter Key -> display simple menu
  777. my ($oldX, $oldY, $oldS, $oldV) = $CON_OUT->Cursor();
  778. my $oldmode = $CON_IN->Mode();
  779. my $choice = &choose_menu($CON_IN, $CON_OUT,
  780. "F1: Reload", "F2: Pause", "F3: Exit",
  781. "F4: Debug", "F5: Logging");
  782. $CON_IN->Mode($oldmode);
  783. $CON_OUT->Cursor($oldX, $oldY, $oldS, $oldV);
  784. $key = $keymap{$choice};
  785. print "action=$choice key=$key\n";
  786. }
  787. else {
  788. $key = $keymap{$key} if $keymap{$key};
  789. }
  790. if ($key eq 'F1') {
  791. print "Key F1 pressed. Reloading code\n";
  792. read_code();
  793. }
  794. elsif ($key eq 'F2') {
  795. print "Key F2 pressed. Sorry, I don't want to pause! Pause is not implemented yet :)\n";
  796. }
  797. elsif ($key eq 'F3') {
  798. print "Key F3 pressed. Exiting\n";
  799. &exit_pgm;
  800. }
  801. elsif ($key eq 'F4') {
  802. &toggle_debug;
  803. }
  804. elsif ($key eq 'F5') {
  805. &toggle_log;
  806. }
  807. elsif ($key) {
  808. print "key press: $key\n" if $config_parms{debug} eq 'misc';
  809. }
  810. }
  811. sub toggle_debug {
  812. $config_parms{debug} = ($config_parms{debug}) ? 0 : 1;
  813. my $state = ($config_parms{debug}) ? 'on' : 'off';
  814. print "Key F4 pressed. Debug turned $state.\n";
  815. }
  816. sub toggle_log {
  817. $config_parms{log} = ($config_parms{log}) ? 0 : 1;
  818. my ($state, $logfile);
  819. if ($config_parms{log}) {
  820. $state = 'on';
  821. $logfile = $config_parms{log_file};
  822. $logfile = 'mh_log.txt' unless $logfile;
  823. print "Key F5 pressed. Output will be logged into $logfile\n";
  824. open(OLDOUT, ">&STDOUT"); # Copy old handle
  825. open(OLDERR, ">&STDERR"); # Copy old handle
  826. open STDOUT, ">>$logfile" or print "\nError, could not open logfile $logfile: $!\n";
  827. $| = 1; # Turn on command buffering (e.g. flush on every print)
  828. open(STDERR, ">&STDOUT");
  829. }
  830. else {
  831. $state = 'off';
  832. print "Key F5 pressed. Output will no longer be logged to $logfile\n";
  833. close STDOUT;
  834. open(STDERR, ">&OLDERR");
  835. open(STDOUT, ">&OLDOUT");
  836. close OLDOUT;
  837. close OLDERR;
  838. print "STDOUT has been restored\n";
  839. }
  840. }
  841. my @serial_data_buffer;
  842. sub check_for_serial_data {
  843. # Process remaining serial items from previous pass
  844. if (my $data = shift @serial_data_buffer) {
  845. print "Running serial_data_buffer string: $data\n" if $config_parms{debug} eq 'X10';
  846. &process_serial_data($data, 1);
  847. return;
  848. }
  849. &check_for_cm11_data if $Serial_Ports{cm11}{object};
  850. &check_for_Homevision_data if $Serial_Ports{Homevision}{object};
  851. &check_for_HomeBase_data if $Serial_Ports{HomeBase}{object};
  852. &check_for_generic_serial_data('weeder') if $Serial_Ports{weeder}{object};
  853. for my $port_name (@Generic_Serial_Ports) {
  854. &check_for_generic_serial_data($port_name) if $Serial_Ports{$port_name}{object};
  855. }
  856. # Leave this under user control?
  857. # &iButton::monitor if $config_parms{ibutton_port} and if $New_Second;
  858. }
  859. sub check_for_socket_data {
  860. # Time to finish the http GET from 2 passes ago with a list of spoken data
  861. if ($leave_socket_open_passes and --$leave_socket_open_passes == 0 and my $sock = $Socket_Ports{http}{socka}) {
  862. print "closing http port with action: $leave_socket_open_action\n" if $config_parms{debug} eq 'http';
  863. my $html = &html_page("", eval($leave_socket_open_action));
  864. print "Error in http lso action: $@\n" if $@;
  865. print $sock $html;
  866. &socket_close('http');
  867. }
  868. my (@ports_with_data, @active_ports);
  869. # See which ports are active
  870. # - could probably use a smarter select check here, rather than loop for each port
  871. for my $port_name (keys %Socket_Ports) {
  872. next unless my $sock = $Socket_Ports{$port_name}{sock};
  873. $Socket_Ports{$port_name}{inactive_this_pass} = 0;
  874. if ($Socket_Ports{$port_name}{socka}) {
  875. push(@active_ports, $port_name);
  876. $Socket_Ports{$port_name}{active_this_pass} = 0;
  877. }
  878. else {
  879. if (my $nfound = &socket_has_data($sock)) {
  880. my $new_sock = $sock->accept();
  881. next unless $new_sock; # Can be undef it socket was killed
  882. $new_sock->autoflush(1); # Not sure if this does anything?
  883. $Socket_Ports{$port_name}{socka} = $new_sock;
  884. $Socket_Ports{$port_name}{active_this_pass} = 1;
  885. delete $Socket_Ports{$port_name}{data}; # Delete data from previous session
  886. push(@active_ports, $port_name);
  887. # Log the address of the client
  888. my $peer = $new_sock->peername;
  889. my ($port, $iaddr) = unpack_sockaddr_in($peer) if $peer;
  890. my $client_ip_address = inet_ntoa($iaddr) if $iaddr;
  891. $Socket_Ports{$port_name}{client_ip_address} = $client_ip_address;
  892. logit("$config_parms{data_dir}/logs/server.$Year_Month_Now.log", "$port_name $client_ip_address");
  893. print "\n$port_name active sock=$new_sock client=$client_ip_address.\n" if $config_parms{debug} eq $port_name;
  894. }
  895. }
  896. }
  897. # See if any active ports have data ... this could be rolled into previous loop
  898. for my $port_name (@active_ports) {
  899. my $sock = $Socket_Ports{$port_name}{socka};
  900. if (my $nfound = &socket_has_data($sock)) {
  901. print "\n$port_name nfound=$nfound\n" if $config_parms{debug} eq $port_name;
  902. if ($nfound < 0) {
  903. # Note, must do a shutdown here ... a close does not close handles
  904. # from &run (system start) processes !?! ... maybe IO sockets do not need this?
  905. # Not sure how to shutdown IO handles ... this gives 'bad symbol on filehandle' error
  906. # shutdown($sock->fileno(), 2); # "how": 0=no more receives, 1=sends, 2=both
  907. print "1 closing socket port $port_name\n" if $config_parms{debug} eq $port_name;
  908. &socket_close($port_name);
  909. }
  910. else {
  911. push(@ports_with_data, $port_name);
  912. }
  913. }
  914. }
  915. # Get data from active ports
  916. for my $port_name (@ports_with_data) {
  917. my $sock = $Socket_Ports{$port_name}{socka};
  918. print "\nchecking port=$port_name sock=$Socket_Ports{$port_name}{socka} lso=$leave_socket_open_passes.\n" if $config_parms{debug} eq $port_name;
  919. my $data;
  920. # Buffer mode means only read one line per pass
  921. # - This allows user code the option of reading port with <>
  922. # - Assumes clients will send a line at a time, so will not block
  923. if ($Socket_Ports{$port_name}{datatype} and $Socket_Ports{$port_name}{datatype} eq 'buffered') {
  924. $data = <$sock>;
  925. }
  926. else {
  927. # 1500 is ethernet packet size
  928. my $from_saddr = recv($sock, $data, 1500, 0);
  929. if ($Socket_Ports{$port_name}{protocol} and $Socket_Ports{$port_name}{protocol} eq 'udp') {
  930. (my $from_port, my $from_ip) = sockaddr_in($from_saddr);
  931. $Socket_Ports{$port_name}{from_port} = $from_port;
  932. $Socket_Ports{$port_name}{from_ip} = $from_ip;
  933. }
  934. }
  935. # Need to do this or the socket never closes!
  936. # For some reason, nfound = 1 (instead of -1) unless we do this.
  937. # In other words, a telnet disconnect will leave nfound=1, but no data.
  938. # When telnet closes, byte IS defined, but is empty, so check on ''
  939. if (!defined $data or $data eq '') {
  940. print "closing socket port $port_name\n" if $config_parms{debug} eq $port_name;
  941. &socket_close($port_name);
  942. }
  943. if ($Socket_Ports{$port_name}{datatype} and $Socket_Ports{$port_name}{datatype} eq 'raw') {
  944. $Socket_Ports{$port_name}{data} = $data; # No not break data on newlines
  945. return;
  946. }
  947. if (my $echo = $config_parms{"${port_name}_echo"}) {
  948. # Need to loop thru $data here, one byte at a time
  949. my $byte = $data;
  950. # bs = 8, del=127
  951. my $char = unpack('C', $byte);
  952. # Allow us to pick our echo character (e.g. '*')
  953. $byte = $echo unless $echo == 1 or $char eq 8;
  954. next if $char eq 8;
  955. print $sock $byte unless $char eq 13 or $char eq 10;
  956. }
  957. $Socket_Ports{$port_name}{data} .= $data if defined $data;
  958. print "$port_name data=$Socket_Ports{$port_name}{data}..\n" if $config_parms{debug} eq $port_name;
  959. # Break data on newlines
  960. next unless $Socket_Ports{$port_name}{data};
  961. while (my($record, $remainder) = $Socket_Ports{$port_name}{data} =~ /(.+?)[\r\n]+(.*)/) {
  962. if ($config_parms{debug} eq $port_name) {
  963. print "$port_name record=$record. hex=", unpack('H*', $record), "\n";
  964. print "$port_name remainder=$remainder. hex=", unpack('H*', $remainder), "\n";
  965. }
  966. $Socket_Ports{$port_name}{data_record} = $record;
  967. $Socket_Ports{$port_name}{data} = $remainder;
  968. if ($port_name eq 'http') {
  969. if ($record =~ /^ *GET /) {
  970. ($leave_socket_open_passes, $leave_socket_open_action) = &process_http_request($sock, $record);
  971. print "db lso=$leave_socket_open_passes sock=$sock.\n" if $config_parms{debug} eq $port_name;
  972. unless ($leave_socket_open_passes) {
  973. print "4 closing socket port $port_name\n" if $config_parms{debug} eq $port_name;
  974. # We must sleep here for a bit, or else Netscape sometimes
  975. # says 'Document contains no data'.
  976. select undef, undef, undef, .010;
  977. &socket_close($port_name);
  978. }
  979. }
  980. else {
  981. # Do nothing with non-GET http requests
  982. # print $sock ">$record";
  983. }
  984. }
  985. else {
  986. # 10/99 Comment out \r\n print ... what needed this?? Messed up viavoice server
  987. # print $sock "\r\n";
  988. # print $sock "You said: $record\n";
  989. # non-raw, non-http socket data is usually read by 'said' Socket_Item methods
  990. # - only 1 per pass, since 'said' only reads one data_record per pass
  991. last;
  992. }
  993. }
  994. }
  995. }
  996. # This is called by mh/lib/Generic_Item.pm
  997. # - it fires tied items/events
  998. sub check_for_tied_events {
  999. my @objects = @_;
  1000. for my $object1 (@objects) {
  1001. my $state1 = lc $object1 -> state;
  1002. my $state_key = $state1;
  1003. print "Object link: starting enumeration for object=$object1 state=$state1\n" if $config_parms{debug} eq 'events';
  1004. for my $key (keys %{$$object1{tied_objects}}) {
  1005. $state_key = 'all_states' unless $$object1{tied_objects}{$key}{$state_key};
  1006. if ($$object1{tied_objects}{$key}{$state_key}) {
  1007. my ($object2, $state2) = @{$$object1{tied_objects}{$key}{$state_key}};
  1008. $state2 = $state1 unless defined $state2;
  1009. print "Object link: Setting $object2 to $state2\n" if $config_parms{debug} eq 'events';
  1010. if ($object2->can('…

Large files files are truncated, but you can click here to view the full file