1-Wire with Leds and Beeps oscar
1-wire-with-leds-and-beeps / software / main.pl
#!/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 ---------------------