#!/usr/bin/perl -w $|=1; use strict; use warnings FATAL => 'all'; use FileHandle; use POSIX; use Getopt::Std; use Time::HiRes qw(usleep); use OWNet ; use File::Pid::Quick ; # qw( verbose ); # There will be only one!... :) my $path=$0; our $fname=substr($path, rindex($path,"/")+1,length($path)-rindex($path,"/")-4); File::Pid::Quick->check("/var/run/$fname.pid"); # Get options and eventually prints help. use vars qw/ %opt /; &init; use constant LOG_NORMAL => 1; # Everydays log use constant LOG_VERBOSE => 1; # Verbose log use constant LOG_DEBUG => 1; # Maximum log, you are looking for problems... use constant GREEN_LED => "/3A.IC2........./PIO.B"; use constant RED_LED => "/3A.IC2........./PIO.A"; use constant BOTH_LED => "/3A.IC2........./PIO.ALL"; use constant BUZZER_ID => "/3A.IC1........./PIO.A"; use constant DOOR_PROBES => "/3A.IC3........./sensed.ALL"; our $FIFO_TO_CHILD_LED = "/var/run/.".$fname.".led.fifo"; our $FIFO_TO_CHILD_BUZ = "/var/run/.".$fname.".buz.fifo"; # Standard log file my $log_file="/var/log/$fname.log"; my $new_stdout=undef; if ($opt{v}) { # This is for STDOUT output # Workaround for buffer problem: at the start, it waits for your first input, tipically forever... :( $new_stdout = FileHandle->new_from_fd(dup(fileno(STDOUT)), 'w'); $new_stdout->autoflush(1); } else { $log_file = $opt{f} if $opt{f}; $new_stdout = FileHandle->new(">> $log_file"); $new_stdout->autoflush(1); } # Which is my server? my $owserver = OWNet->new( "localhost:4304" ) ; # So, to know if the connection is up&runnig, we must to try to talk with it... my $temp_dir_list = $owserver->dir('/') || die ("Cannot open OWNet connection"); print { $new_stdout } (strftime "%b %e %H:%M:%S", localtime). " Initial dir_list to chech owserver connection: $temp_dir_list\n" if LOG_DEBUG; print { $new_stdout } (strftime "%b %e %H:%M:%S", localtime). " Succesfully connected to owserver.\n" if LOG_NORMAL; # These are the standard elements of our bus # We can choice: ignore it, or check their presence at every cycle # as bus status check... # I'm using it now to get rid of short-circuit on the bus: there must be some well-known elements (8th, 9th, 10th... -> (*) ), else repeat the scan (look in the scan_bus sub) # To fill up this array correctly, you can start the script with LOG_DEBUG and no iButtons: firsts dir scan will give you the list of items found (just 2 print backwards) my @keys_known = ( "/bus.0", "/settings", "/system", "/statistics", "/structure", "/simultaneous", "/alarm", "/3A.IC2.........", # Switch used for leds # this (# 7) is used to check bus consistency in sub scan_bus "/3A.IC1.........", # Switch for beeper (and an unused port) # this (# 8) is used to check bus consistency in sub scan_bus "/3A.IC3.........", # Switch for door probe # this (# 9) is used to check bus consistency in sub scan_bus "/81.DS9490R....." ); # 1-wire master usb stick # These are the well-known authorized keys: only these can change the status of the alarm (idle, active, etc.). my @keys_auth = ( # "/01.ID9000000000", # # "/01.ID8000000000", # # "/01.ID7000000000", # # "/01.ID6000000000", # # "/01.ID5000000000", # # "/01.ID4000000000", # "/01.ID3000000000", # Mr Smith Sr "/01.ID2000000000", # Mr. Smith Jr "/01.ID1000000000", # Mrs. Smith "/01.ID0000000000"); # Mr. Smith # This will contain what we found on the bus my @keys_found; # I memorize the status of the system to make it persistent: in the case of a reboot, restart the script again in the state it was in before ... (it can be active/inactive). # Uhm... Should I add an "alarmed" status?? my $persistence = "/var/cache/$fname.status"; # Semaforo sullo status antifurto: 0=inactive - 1=active my $status=0; # Alarm status my $alarm=0; # iButton found: 0=none - 1=iButton present on bus my $button=0; # How much time I'll sleep between cycles of the infinite loop? It influences the sensibility of iButton contact... my $cycle_delay=75000; # 75/100 of second # If you activate the system (iButton present on bus) while the door is open, start a pre-alert period (about 30"?) # to deactivate it, before the alarm. This is only for the door. # Timing is in cycles number my $prealarm=0; my @found_auth; my %tmp_hash; @tmp_hash{@keys_auth} = undef; my $ret_system; my $pid_led_child=0; my $pid_buz_child=0; # ------------ SUB sub write_to_fifo_led { open FILE1, ">", $FIFO_TO_CHILD_LED or die $!; print FILE1 $_[0]; close FILE1; kill USR1 => $pid_led_child; } sub write_to_fifo_buz { open FILE2, ">", $FIFO_TO_CHILD_BUZ or die $!; print FILE2 $_[0]; close FILE2; kill USR1 => $pid_buz_child; } sub scan_bus { no warnings; #I don't remember exactly why... May be short circuit related.... my $dir_list = undef; do { $dir_list = $owserver->dir('/'); # Get rid of short-circuit on the bus : there must be some well-known elements, else repeat the scan.. } until (index($dir_list,$keys_known[7]) != -1 && index($dir_list,$keys_known[8]) != -1 && index($dir_list,$keys_known[9]) != -1 ); @keys_found = split /,/ , $dir_list; print { $new_stdout } (strftime "%b %e %H:%M:%S", localtime). " dir_list $dir_list\n" if LOG_DEBUG; } # Read persistence status, if file present sub read_persistence { if (-e $persistence) { my %v_persistence; open my $v_persistence, '<', $persistence or die $!; while (<$v_persistence>) { chomp; (my $key, my @value) = split/=/, $_; $v_persistence{$key} = join '=', @value; } close $v_persistence; if ( $v_persistence{'status'} eq "active" ) { $status=1; # Tipically this will be after a reboot, so I must activate all related things... if (!$opt{Z}) { $ret_system = system("power_on.sh"); print { $new_stdout } (strftime "%b %e %H:%M:%S", localtime). " Starting ZM ($ret_system)\n" if LOG_VERBOSE || LOG_DEBUG; } } else { $status=0; if (!$opt{Z}) { $ret_system = system("power_off.sh"); print { $new_stdout } (strftime "%b %e %H:%M:%S", localtime). " Stopping ZM ($ret_system)\n" if LOG_VERBOSE || LOG_DEBUG; } } print { $new_stdout } (strftime "%b %e %H:%M:%S", localtime). " Persistence=$v_persistence{'status'}\n" if LOG_NORMAL || LOG_VERBOSE || LOG_DEBUG; } } # Brute code optimizing: write for persistence sub write_persistence { open FILE, "+>", $persistence or die $!; print FILE $_[0]; close FILE; } # Look for well-known authorized iButton in the found objects sub finding_auth { @found_auth = grep { exists $tmp_hash{$_}} @keys_found; if (@found_auth) { print { $new_stdout } (strftime "%b %e %H:%M:%S", localtime). " Found: @found_auth \n" if LOG_VERBOSE || LOG_DEBUG; $button=1; } } # Kill-catch: I let all clean (lights, sounds,...) # The state has already been stored when it changed use sigtrap qw(handler signal_handler normal-signals error-signals old-interface-signals ); sub signal_handler { print { $new_stdout } (strftime "%b %e %H:%M:%S", localtime). " ---------Catching signal $SIG{INT} @_ , Cleaning up--------\n" if LOG_NORMAL || LOG_VERBOSE || LOG_DEBUG; # Kill forked childs... kill SIGKILL, $pid_led_child; kill SIGKILL, $pid_buz_child; # I just killed childs, now I reset switches directly: more quick than let the children clean... :) # Power off leds $owserver->write( BOTH_LED , "0,0" ); # and especially ... I turn off the Buzzer!!! :) $owserver->write( BUZZER_ID , 0 ); # ZoneManager is let as is # Its status has eventually changed in another way: keeping good the persistence.... unlink($FIFO_TO_CHILD_LED); unlink($FIFO_TO_CHILD_BUZ); close $new_stdout; exit(0); } # To get signals from outside the script: I can go in alarm status from another script or program: ZoneMinder... :) # Activated with a "kill -SIGUSR1 $pid" # (you can found the pid in /tmp and /var/run. Look at the "use" in head of this script...) use sigtrap 'handler' => \&Triggers_Alarm, 'USR1'; sub Triggers_Alarm { print { $new_stdout } (strftime "%b %e %H:%M:%S", localtime). " --------- ALARM: someone tripped the alarm --------\n---------Catching signal $SIG{INT} @_ , Cleaning up--------\n" if LOG_NORMAL || LOG_VERBOSE || LOG_DEBUG; &be_alarmed(1); # We need to activate other external objects? } sub check_door { # Return : 0 = All ok: door closed, sensor ok # 1 = Door opened, sensor ok # 2 = Door closed, sensor ripped # 3 = Door opened AND sensor ripped my $door_opened = 0; my $door_ripped = 0; my $result = 0; my $temp_read_porta = $owserver->read(DOOR_PROBES); ($door_ripped, $door_opened) = split ( /,/, $temp_read_porta); print { $new_stdout } (strftime "%b %e %H:%M:%S", localtime). " Temp_Read_Porta: $temp_read_porta - Door_Opened: $door_opened Door_Ripped: $door_ripped \n" if LOG_DEBUG; if ($door_opened) { $result++; } if ($door_ripped) { $result+=2;} return $result; } sub be_alarmed { if ( $_[0] == "0" ) { print { $new_stdout } (strftime "%b %e %H:%M:%S", localtime). " All back to normal state... \n" if LOG_NORMAL || LOG_VERBOSE || LOG_DEBUG; $prealarm=0; $alarm=0; # Slow green flashing write_to_fifo_led("G"); # Buzzer silenced write_to_fifo_buz("S"); } elsif ( $_[0] == "1" ) { print { $new_stdout } (strftime "%b %e %H:%M:%S", localtime). " Prealarm... \n" if LOG_NORMAL || LOG_VERBOSE || LOG_DEBUG; $prealarm=1; # Quick red flashing write_to_fifo_led("AR"); # Buzzer in prealarm, slow beep write_to_fifo_buz("P"); } elsif ( $_[0] == "2" ) { $alarm=1; # Quick red flashing write_to_fifo_led("AR"); # Continuous buzzer write_to_fifo_buz("A"); print { $new_stdout } (strftime "%b %e %H:%M:%S", localtime). " Alarm!!! \n" if LOG_NORMAL || LOG_VERBOSE || LOG_DEBUG; } } sub init() { # Take options from command line... getopts("hvTZf:", \%opt) or usage(); usage() if $opt{h}; } sub usage() { print STDERR << "EOF"; This script handles the 1-Wire sub-system of my integrated alarm system. - iButton detection; - door status detection; - led status; - prealert buzzer. usage: $fname [-h] [-Z] [v|f [filename]] -h : this (help) message -v : output on screen -Z : disable ZM & PoE control -f filename : output on file (default) (default filename: /var/log/$fname.log) example: $fname -v -f file EOF exit; } # ----------- MAIN print { $new_stdout } (strftime "%b %e %H:%M:%S", localtime). " Starting... (pid: $$)\n" if LOG_NORMAL || LOG_VERBOSE || LOG_DEBUG; &read_persistence; $pid_led_child=fork(); if ($pid_led_child==0) { #CHILD LED require "./mod_led.pl" } else { $pid_buz_child=fork(); if ($pid_buz_child==0) { #CHILD BUZZER require "./mod_buzzer.pl" } else { # PARENT # Let the children starts... From now they will be autonomous sleep 1; system('touch',$FIFO_TO_CHILD_LED); print { $new_stdout } (strftime "%b %e %H:%M:%S", localtime). " PID Led child: $pid_led_child\n" if LOG_VERBOSE || LOG_DEBUG; system('touch',$FIFO_TO_CHILD_BUZ); print { $new_stdout } (strftime "%b %e %H:%M:%S", localtime). " PID Buzzer child: $pid_buz_child\n" if LOG_VERBOSE || LOG_DEBUG; # Align the led with the persistence: default is green (status=0) if ($status) { write_to_fifo_led("R"); } # Buzzer starts always quiet... write_to_fifo_buz("S"); # Main infinite loop while ( 1 ) { &scan_bus; # When idle, in pre or in alarmed status, I don't need to check the door status, so I can save some unnecessary bus access... if (!$prealarm && !$alarm && $status) { if (&check_door) { &be_alarmed(2); } } # Look for authorized iButtons! &finding_auth; # Gotcha! if ($button) { $status=!$status; print { $new_stdout } (strftime "%b %e %H:%M:%S", localtime). " Authorized iButton found! (@found_auth)\n" if LOG_NORMAL || LOG_VERBOSE || LOG_DEBUG; if ($status) { # Slow red flashing write_to_fifo_led("R"); if (&check_door) { # Alarm just activated: only pre-alarm... &be_alarmed(1); } # Update persistence write_persistence("status=active"); # ZM: Activate ZoneMinder if (!$opt{Z}) { $ret_system = system("power_on.sh"); print { $new_stdout } (strftime "%b %e %H:%M:%S", localtime). " Starting ZM ($ret_system)\n" if LOG_VERBOSE || LOG_DEBUG; } } else { &be_alarmed(0); # Update persistence write_persistence("status=inactive"); # ZM: Deactivate ZoneMinder if (!$opt{Z}) { $ret_system = system("power_off.sh"); print { $new_stdout } (strftime "%b %e %H:%M:%S", localtime). " Shutting down ZM ($ret_system)\n" if LOG_VERBOSE || LOG_DEBUG; } } # Long pause (with led lighted) # So I can avoid rebound for a log iButton pressure (or for cache problem?) if (!$prealarm) { usleep(5000000); # 5 seconds } # Reset del semaforo di presenza dell'iButton... $button=0; } usleep($cycle_delay); } } } # END ---------------------