1-Wire with Leds and Beeps oscar

1-wire-with-leds-and-beeps / software / main.pl

14.7 KB · raw

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