2019-09-24 00:54:27 +08:00
|
|
|
#!@PERL_EXECUTABLE@ -wT
|
2009-06-08 17:22:28 +08:00
|
|
|
#
|
|
|
|
# ==========================================================================
|
|
|
|
#
|
2017-07-14 22:28:31 +08:00
|
|
|
# ZoneMinder External Trigger Script
|
2009-06-08 17:22:28 +08:00
|
|
|
# Copyright (C) 2001-2008 Philip Coombes
|
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or
|
|
|
|
# modify it under the terms of the GNU General Public License
|
|
|
|
# as published by the Free Software Foundation; either version 2
|
|
|
|
# of the License, or (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program; if not, write to the Free Software
|
2016-12-26 23:23:16 +08:00
|
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
2009-06-08 17:22:28 +08:00
|
|
|
#
|
|
|
|
# ==========================================================================
|
2015-04-16 13:05:47 +08:00
|
|
|
|
2009-06-08 17:22:28 +08:00
|
|
|
use strict;
|
|
|
|
use bytes;
|
|
|
|
|
|
|
|
# ==========================================================================
|
|
|
|
#
|
|
|
|
# User config
|
|
|
|
#
|
|
|
|
# ==========================================================================
|
|
|
|
|
|
|
|
use constant MAX_CONNECT_DELAY => 10;
|
2017-01-13 01:56:55 +08:00
|
|
|
use constant MONITOR_RELOAD_INTERVAL => 300;
|
|
|
|
use constant SELECT_TIMEOUT => 0.25;
|
2009-06-08 17:22:28 +08:00
|
|
|
|
|
|
|
# ==========================================================================
|
|
|
|
#
|
|
|
|
# Channel/Connection Modules
|
|
|
|
#
|
|
|
|
# ==========================================================================
|
|
|
|
|
|
|
|
@EXTRA_PERL_LIB@
|
2011-01-06 01:35:25 +08:00
|
|
|
use ZoneMinder;
|
2009-06-08 17:22:28 +08:00
|
|
|
use ZoneMinder::Trigger::Channel::Inet;
|
|
|
|
use ZoneMinder::Trigger::Channel::Unix;
|
|
|
|
use ZoneMinder::Trigger::Channel::Serial;
|
|
|
|
use ZoneMinder::Trigger::Connection;
|
|
|
|
|
|
|
|
my @connections;
|
2015-04-16 13:05:47 +08:00
|
|
|
push( @connections,
|
2017-01-11 04:29:54 +08:00
|
|
|
ZoneMinder::Trigger::Connection->new(
|
2017-01-13 01:54:40 +08:00
|
|
|
name=>'Chan1 TCP on port 6802',
|
2017-01-11 04:29:54 +08:00
|
|
|
channel=>ZoneMinder::Trigger::Channel::Inet->new( port=>6802 ),
|
2017-01-13 01:54:40 +08:00
|
|
|
mode=>'rw'
|
2015-04-16 13:05:47 +08:00
|
|
|
)
|
2017-01-11 04:29:54 +08:00
|
|
|
);
|
2015-04-16 13:05:47 +08:00
|
|
|
push( @connections,
|
2017-01-11 04:29:54 +08:00
|
|
|
ZoneMinder::Trigger::Connection->new(
|
2017-01-13 01:54:40 +08:00
|
|
|
name=>'Chan2 Unix Socket at ' . $Config{ZM_PATH_SOCKS}.'/zmtrigger.sock',
|
2017-01-11 04:29:54 +08:00
|
|
|
channel=>ZoneMinder::Trigger::Channel::Unix->new(
|
|
|
|
path=>$Config{ZM_PATH_SOCKS}.'/zmtrigger.sock'
|
|
|
|
),
|
2017-01-13 01:54:40 +08:00
|
|
|
mode=>'rw'
|
2015-04-16 13:05:47 +08:00
|
|
|
)
|
2017-01-11 04:29:54 +08:00
|
|
|
);
|
2017-01-13 01:54:40 +08:00
|
|
|
#push( @connections, ZoneMinder::Trigger::Connection->new( name=>'Chan3', channel=>ZoneMinder::Trigger::Channel::File->new( path=>'/tmp/zmtrigger.out' ), mode=>'w' ) );
|
|
|
|
#push( @connections, ZoneMinder::Trigger::Connection->new( name=>'Chan4', channel=>ZoneMinder::Trigger::Channel::Serial->new( path=>'/dev/ttyS0' ), mode=>'rw' ) );
|
2009-06-08 17:22:28 +08:00
|
|
|
|
|
|
|
# ==========================================================================
|
|
|
|
#
|
|
|
|
# Don't change anything from here on down
|
|
|
|
#
|
|
|
|
# ==========================================================================
|
|
|
|
|
|
|
|
use DBI;
|
|
|
|
#use Socket;
|
2015-04-16 13:42:56 +08:00
|
|
|
use autouse 'Data::Dumper'=>qw(Dumper);
|
2009-06-08 17:22:28 +08:00
|
|
|
use POSIX qw( EINTR );
|
|
|
|
use Time::HiRes qw( usleep );
|
|
|
|
|
|
|
|
$| = 1;
|
|
|
|
|
2016-03-12 05:28:16 +08:00
|
|
|
$ENV{PATH} = '/bin:/usr/bin:/usr/local/bin';
|
2009-06-08 17:22:28 +08:00
|
|
|
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
|
|
|
|
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
|
|
|
|
|
2011-06-21 17:19:10 +08:00
|
|
|
logInit();
|
|
|
|
logSetSignal();
|
2021-03-18 01:09:54 +08:00
|
|
|
my $zm_terminate = 0;
|
|
|
|
sub TermHandler {
|
|
|
|
Info('Received TERM, exiting');
|
|
|
|
$zm_terminate = 1;
|
|
|
|
}
|
|
|
|
$SIG{TERM} = \&TermHandler;
|
|
|
|
$SIG{INT} = \&TermHandler;
|
2009-06-08 17:22:28 +08:00
|
|
|
|
2019-08-09 02:11:24 +08:00
|
|
|
Info('Trigger daemon starting');
|
2009-06-08 17:22:28 +08:00
|
|
|
|
|
|
|
my $dbh = zmDbConnect();
|
|
|
|
|
|
|
|
my $base_rin = '';
|
2017-01-13 01:54:40 +08:00
|
|
|
foreach my $connection ( @connections ) {
|
2019-08-09 02:11:24 +08:00
|
|
|
Info("Opening connection '$connection->{name}'");
|
2017-01-11 04:29:54 +08:00
|
|
|
$connection->open();
|
2009-06-08 17:22:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
my @in_select_connections = grep { $_->input() && $_->selectable() } @connections;
|
|
|
|
my @in_poll_connections = grep { $_->input() && !$_->selectable() } @connections;
|
|
|
|
my @out_connections = grep { $_->output() } @connections;
|
|
|
|
|
2017-01-13 01:54:40 +08:00
|
|
|
foreach my $connection ( @in_select_connections ) {
|
2017-01-11 04:29:54 +08:00
|
|
|
vec( $base_rin, $connection->fileno(), 1 ) = 1;
|
2009-06-08 17:22:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
my %spawned_connections;
|
|
|
|
my %monitors;
|
|
|
|
my $monitor_reload_time = 0;
|
2018-10-11 02:27:05 +08:00
|
|
|
my @needsReload;
|
2017-01-13 02:03:53 +08:00
|
|
|
loadMonitors();
|
|
|
|
|
2009-06-08 17:22:28 +08:00
|
|
|
$! = undef;
|
|
|
|
my $rin = '';
|
|
|
|
my $win = $rin;
|
|
|
|
my $ein = $win;
|
|
|
|
my $timeout = SELECT_TIMEOUT;
|
|
|
|
my %actions;
|
2021-03-18 01:09:54 +08:00
|
|
|
while (!$zm_terminate) {
|
2017-01-11 04:29:54 +08:00
|
|
|
$rin = $base_rin;
|
|
|
|
# Add the file descriptors of any spawned connections
|
2019-08-09 02:11:24 +08:00
|
|
|
foreach my $fileno ( keys %spawned_connections ) {
|
|
|
|
vec($rin, $fileno, 1) = 1;
|
2017-01-11 04:29:54 +08:00
|
|
|
}
|
|
|
|
|
2019-08-09 02:11:24 +08:00
|
|
|
my $nfound = select(my $rout = $rin, undef, my $eout = $ein, $timeout);
|
2017-01-11 04:29:54 +08:00
|
|
|
if ( $nfound > 0 ) {
|
2019-08-09 02:11:24 +08:00
|
|
|
Debug("Got input from $nfound connections");
|
2017-01-11 04:29:54 +08:00
|
|
|
foreach my $connection ( @in_select_connections ) {
|
2019-08-09 02:11:24 +08:00
|
|
|
if ( vec($rout, $connection->fileno(), 1) ) {
|
|
|
|
Debug('Got input from connection '
|
2017-01-11 04:29:54 +08:00
|
|
|
.$connection->name()
|
2017-07-14 22:28:31 +08:00
|
|
|
.' ('
|
2017-01-11 04:29:54 +08:00
|
|
|
.$connection->fileno()
|
2019-08-09 02:11:24 +08:00
|
|
|
.')'
|
2017-01-11 04:29:54 +08:00
|
|
|
);
|
|
|
|
if ( $connection->spawns() ) {
|
|
|
|
my $new_connection = $connection->accept();
|
|
|
|
$spawned_connections{$new_connection->fileno()} = $new_connection;
|
2019-08-09 02:11:24 +08:00
|
|
|
Debug('Added new spawned connection ('
|
2017-01-11 04:29:54 +08:00
|
|
|
.$new_connection->fileno()
|
2017-07-14 22:28:31 +08:00
|
|
|
.'), '
|
2017-01-11 04:29:54 +08:00
|
|
|
.int(keys(%spawned_connections))
|
2019-08-09 02:11:24 +08:00
|
|
|
.' spawned connections'
|
2017-01-11 04:29:54 +08:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
my $messages = $connection->getMessages();
|
|
|
|
if ( defined($messages) ) {
|
|
|
|
foreach my $message ( @$messages ) {
|
|
|
|
handleMessage( $connection, $message );
|
2015-04-16 13:05:47 +08:00
|
|
|
}
|
2017-01-11 04:29:54 +08:00
|
|
|
}
|
2019-08-09 02:11:24 +08:00
|
|
|
} # end if connection->spawns
|
|
|
|
} # end if vec
|
2017-01-11 04:29:54 +08:00
|
|
|
} # end foreach connection
|
|
|
|
|
|
|
|
foreach my $connection ( values(%spawned_connections) ) {
|
2019-08-09 02:11:24 +08:00
|
|
|
if ( vec($rout, $connection->fileno(), 1) ) {
|
|
|
|
Debug('Got input from spawned connection '
|
2021-12-11 04:36:47 +08:00
|
|
|
.$connection->name().' ('.$connection->fileno().')');
|
2015-04-16 13:05:47 +08:00
|
|
|
my $messages = $connection->getMessages();
|
2021-12-11 04:36:47 +08:00
|
|
|
if (defined($messages)) {
|
2017-01-11 04:29:54 +08:00
|
|
|
foreach my $message ( @$messages ) {
|
2019-08-09 02:11:24 +08:00
|
|
|
handleMessage($connection, $message);
|
2017-01-11 04:29:54 +08:00
|
|
|
}
|
|
|
|
} else {
|
2019-08-09 02:11:24 +08:00
|
|
|
delete $spawned_connections{$connection->fileno()};
|
|
|
|
Debug('Removed spawned connection ('
|
2017-01-11 04:29:54 +08:00
|
|
|
.$connection->fileno()
|
2017-07-14 22:28:31 +08:00
|
|
|
.'), '
|
2017-01-11 04:29:54 +08:00
|
|
|
.int(keys(%spawned_connections))
|
2019-08-09 02:11:24 +08:00
|
|
|
.' spawned connections'
|
2017-01-11 04:29:54 +08:00
|
|
|
);
|
|
|
|
$connection->close();
|
2015-04-16 13:05:47 +08:00
|
|
|
}
|
2017-01-11 04:29:54 +08:00
|
|
|
}
|
2017-07-14 22:28:31 +08:00
|
|
|
} # end foreach spawned connection
|
2017-01-11 04:29:54 +08:00
|
|
|
} elsif ( $nfound < 0 ) {
|
|
|
|
if ( $! == EINTR ) {
|
|
|
|
# Do nothing
|
|
|
|
} else {
|
2019-08-09 02:11:24 +08:00
|
|
|
Fatal("Can't select: $!");
|
2015-04-16 13:05:47 +08:00
|
|
|
}
|
2020-11-12 05:12:30 +08:00
|
|
|
} # end if select returned activity
|
2017-01-11 04:29:54 +08:00
|
|
|
|
|
|
|
# Check polled connections
|
|
|
|
foreach my $connection ( @in_poll_connections ) {
|
|
|
|
my $messages = $connection->getMessages();
|
2021-12-11 04:36:47 +08:00
|
|
|
if (defined($messages)) {
|
2021-12-13 06:01:12 +08:00
|
|
|
foreach my $message (@$messages) { handleMessage($connection, $message) };
|
2017-01-11 04:29:54 +08:00
|
|
|
}
|
|
|
|
}
|
2015-04-16 13:05:47 +08:00
|
|
|
|
2017-01-26 06:14:30 +08:00
|
|
|
# Check for alarms that might have happened
|
2017-01-11 04:29:54 +08:00
|
|
|
my @out_messages;
|
2019-08-09 02:11:24 +08:00
|
|
|
foreach my $monitor ( values %monitors ) {
|
2021-12-11 04:36:47 +08:00
|
|
|
if ($$monitor{Function} eq 'None') {
|
|
|
|
$monitor_reload_time = 0;
|
|
|
|
next;
|
|
|
|
}
|
2016-02-22 07:55:04 +08:00
|
|
|
|
2021-12-11 04:36:47 +08:00
|
|
|
if (!zmMemVerify($monitor)) {
|
2017-01-26 06:14:30 +08:00
|
|
|
# Our attempt to verify the memory handle failed. We should reload the monitors.
|
|
|
|
# Don't need to zmMemInvalidate because the monitor reload will do it.
|
2018-10-11 02:27:05 +08:00
|
|
|
push @needsReload, $monitor;
|
2017-01-11 04:29:54 +08:00
|
|
|
next;
|
2015-04-16 13:05:47 +08:00
|
|
|
}
|
2016-02-22 07:55:04 +08:00
|
|
|
|
2021-12-11 04:36:47 +08:00
|
|
|
my ($state, $last_event) = zmMemRead($monitor, [
|
2017-07-14 22:28:31 +08:00
|
|
|
'shared_data:state',
|
|
|
|
'shared_data:last_event'
|
2021-12-11 04:36:47 +08:00
|
|
|
]);
|
2017-01-11 04:29:54 +08:00
|
|
|
|
2021-12-11 04:36:47 +08:00
|
|
|
if ($state == STATE_ALARM or $state == STATE_ALERT) {
|
2017-07-14 22:28:31 +08:00
|
|
|
# In alarm state
|
2017-01-11 04:29:54 +08:00
|
|
|
if ( !defined($monitor->{LastEvent})
|
2021-04-08 02:15:51 +08:00
|
|
|
or ($last_event != $monitor->{LastEvent})
|
2017-07-14 22:28:31 +08:00
|
|
|
) {
|
|
|
|
# A new event
|
2019-08-09 02:11:24 +08:00
|
|
|
push @out_messages, $monitor->{Id}.'|on|'.time().'|'.$last_event;
|
2017-07-14 22:28:31 +08:00
|
|
|
} else {
|
|
|
|
# The same one as last time, so ignore it
|
2017-01-11 04:29:54 +08:00
|
|
|
# Do nothing
|
|
|
|
}
|
2017-07-14 22:28:31 +08:00
|
|
|
} elsif (
|
2021-04-08 02:15:51 +08:00
|
|
|
(($state == STATE_IDLE) and ($monitor->{LastState} != STATE_IDLE))
|
|
|
|
or
|
|
|
|
(($state == STATE_TAPE) and ($monitor->{LastState} != STATE_TAPE))
|
2017-07-14 22:28:31 +08:00
|
|
|
) {
|
|
|
|
# Out of alarm state
|
2019-08-09 02:11:24 +08:00
|
|
|
push @out_messages, $monitor->{Id}.'|off|'.time().'|'.$last_event;
|
2017-07-14 22:28:31 +08:00
|
|
|
} elsif (
|
|
|
|
defined($monitor->{LastEvent})
|
|
|
|
&&
|
|
|
|
($last_event != $monitor->{LastEvent})
|
|
|
|
) {
|
|
|
|
# We've missed a whole event
|
2019-08-09 02:11:24 +08:00
|
|
|
push @out_messages, $monitor->{Id}.'|on|'.time().'|'.$last_event;
|
|
|
|
push @out_messages, $monitor->{Id}.'|off|'.time().'|'.$last_event;
|
2015-04-16 13:05:47 +08:00
|
|
|
}
|
2017-01-11 04:29:54 +08:00
|
|
|
$monitor->{LastState} = $state;
|
|
|
|
$monitor->{LastEvent} = $last_event;
|
|
|
|
} # end foreach monitor
|
2019-08-09 02:11:24 +08:00
|
|
|
|
2017-01-11 04:29:54 +08:00
|
|
|
foreach my $connection ( @out_connections ) {
|
|
|
|
if ( $connection->canWrite() ) {
|
2019-08-09 02:11:24 +08:00
|
|
|
$connection->putMessages(\@out_messages);
|
2015-04-16 13:05:47 +08:00
|
|
|
}
|
2017-01-11 04:29:54 +08:00
|
|
|
}
|
2019-08-09 02:11:24 +08:00
|
|
|
|
|
|
|
foreach my $connection ( values %spawned_connections ) {
|
2017-01-11 04:29:54 +08:00
|
|
|
if ( $connection->canWrite() ) {
|
2019-08-09 02:11:24 +08:00
|
|
|
$connection->putMessages(\@out_messages);
|
2015-04-16 13:05:47 +08:00
|
|
|
}
|
2017-01-11 04:29:54 +08:00
|
|
|
}
|
|
|
|
|
2017-01-13 01:54:40 +08:00
|
|
|
if ( my @action_times = keys(%actions) ) {
|
2019-08-09 02:11:24 +08:00
|
|
|
Debug('Checking for timed actions');
|
2017-01-13 01:54:40 +08:00
|
|
|
my $now = time();
|
|
|
|
foreach my $action_time ( sort( grep { $_ < $now } @action_times ) ) {
|
2020-11-12 05:12:30 +08:00
|
|
|
Info('Found '.(scalar @{$actions{$action_time}}).'actions expiring at '.$action_time);
|
2017-01-13 01:54:40 +08:00
|
|
|
foreach my $action ( @{$actions{$action_time}} ) {
|
|
|
|
my $connection = $action->{connection};
|
2019-08-09 02:38:27 +08:00
|
|
|
Info("Found action '$$action{message}'");
|
|
|
|
handleMessage($connection, $$action{message});
|
2017-01-13 01:54:40 +08:00
|
|
|
}
|
2019-08-09 02:11:24 +08:00
|
|
|
delete $actions{$action_time};
|
2015-04-16 13:05:47 +08:00
|
|
|
}
|
2017-01-13 01:54:40 +08:00
|
|
|
} # end if have timed actions
|
2017-01-11 04:29:54 +08:00
|
|
|
|
|
|
|
# Allow connections to do their own timed actions
|
|
|
|
foreach my $connection ( @connections ) {
|
|
|
|
my $messages = $connection->timedActions();
|
|
|
|
if ( defined($messages) ) {
|
|
|
|
foreach my $message ( @$messages ) {
|
2019-08-09 02:11:24 +08:00
|
|
|
handleMessage($connection, $message);
|
2017-01-11 04:29:54 +08:00
|
|
|
}
|
2015-04-16 13:05:47 +08:00
|
|
|
}
|
2017-01-11 04:29:54 +08:00
|
|
|
}
|
2019-08-09 02:11:24 +08:00
|
|
|
|
|
|
|
foreach my $connection ( values %spawned_connections ) {
|
2017-01-11 04:29:54 +08:00
|
|
|
my $messages = $connection->timedActions();
|
|
|
|
if ( defined($messages) ) {
|
|
|
|
foreach my $message ( @$messages ) {
|
2019-08-09 02:11:24 +08:00
|
|
|
handleMessage($connection, $message);
|
2017-01-11 04:29:54 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2015-04-16 13:05:47 +08:00
|
|
|
|
2018-10-11 02:27:05 +08:00
|
|
|
# Reload all monitors from the dB every MONITOR_RELOAD_INTERVAL
|
|
|
|
if ( (time() - $monitor_reload_time) > MONITOR_RELOAD_INTERVAL ) {
|
2017-01-11 04:29:54 +08:00
|
|
|
foreach my $monitor ( values(%monitors) ) {
|
2018-10-11 02:27:05 +08:00
|
|
|
zmMemInvalidate( $monitor ); # Free up any used memory handle
|
2015-04-16 13:05:47 +08:00
|
|
|
}
|
2017-01-11 04:29:54 +08:00
|
|
|
loadMonitors();
|
2018-10-11 02:27:05 +08:00
|
|
|
@needsReload = (); # We just reloaded all monitors so no need reload a specific monitor
|
|
|
|
# If we have NOT just reloaded all monitors, reload a specific monitor if its shared mem changed
|
|
|
|
} elsif ( @needsReload ) {
|
|
|
|
foreach my $monitor ( @needsReload ) {
|
|
|
|
loadMonitor($monitor);
|
|
|
|
}
|
|
|
|
@needsReload = ();
|
2017-01-11 04:29:54 +08:00
|
|
|
}
|
2018-10-11 02:27:05 +08:00
|
|
|
|
2017-07-14 22:30:12 +08:00
|
|
|
# zmDbConnect will ping and reconnect if neccessary
|
|
|
|
$dbh = zmDbConnect();
|
2021-03-18 01:09:54 +08:00
|
|
|
} # end while (!$zm_terminate)
|
2018-12-21 02:56:57 +08:00
|
|
|
Info('Trigger daemon exiting');
|
2009-06-08 17:22:28 +08:00
|
|
|
exit;
|
|
|
|
|
2018-10-11 02:27:05 +08:00
|
|
|
sub loadMonitor {
|
|
|
|
my $monitor = shift;
|
|
|
|
|
2020-11-12 05:12:30 +08:00
|
|
|
Debug('Loading monitor '.$monitor);
|
2019-08-09 02:11:24 +08:00
|
|
|
zmMemInvalidate($monitor);
|
2018-10-11 02:27:05 +08:00
|
|
|
|
2019-08-09 02:11:24 +08:00
|
|
|
if ( zmMemVerify($monitor) ) { # This will re-init shared memory
|
|
|
|
$monitor->{LastState} = zmGetMonitorState($monitor);
|
|
|
|
$monitor->{LastEvent} = zmGetLastEvent($monitor);
|
2018-10-11 02:27:05 +08:00
|
|
|
}
|
2019-08-09 02:11:24 +08:00
|
|
|
} # end sub loadMonitor
|
2018-10-11 02:27:05 +08:00
|
|
|
|
2017-01-11 04:29:54 +08:00
|
|
|
sub loadMonitors {
|
|
|
|
$monitor_reload_time = time();
|
2015-04-16 13:05:47 +08:00
|
|
|
|
2017-01-11 04:29:54 +08:00
|
|
|
my %new_monitors = ();
|
2015-04-16 13:05:47 +08:00
|
|
|
|
2020-01-22 05:53:43 +08:00
|
|
|
my $sql = 'SELECT * FROM `Monitors`
|
2020-11-12 05:12:30 +08:00
|
|
|
WHERE find_in_set( `Function`, \'Modect,Mocord,Nodect,Record\' )'.
|
2020-01-22 05:53:43 +08:00
|
|
|
( $Config{ZM_SERVER_ID} ? ' AND `ServerId`=?' : '' )
|
2015-04-16 13:05:47 +08:00
|
|
|
;
|
2017-01-11 04:29:54 +08:00
|
|
|
my $sth = $dbh->prepare_cached( $sql )
|
|
|
|
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
|
|
|
|
my $res = $sth->execute( $Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID} : () )
|
|
|
|
or Fatal( "Can't execute: ".$sth->errstr() );
|
2019-08-09 02:11:24 +08:00
|
|
|
|
|
|
|
while ( my $monitor = $sth->fetchrow_hashref() ) {
|
|
|
|
if ( zmMemVerify($monitor) ) { # This will re-init shared memory
|
|
|
|
$monitor->{LastState} = zmGetMonitorState($monitor);
|
|
|
|
$monitor->{LastEvent} = zmGetLastEvent($monitor);
|
2015-04-16 13:05:47 +08:00
|
|
|
}
|
2017-01-11 04:29:54 +08:00
|
|
|
$new_monitors{$monitor->{Id}} = $monitor;
|
|
|
|
} # end while fetchrow
|
|
|
|
%monitors = %new_monitors;
|
2020-11-13 23:40:55 +08:00
|
|
|
} # end sub loadMonitors
|
2009-06-08 17:22:28 +08:00
|
|
|
|
2017-01-11 04:29:54 +08:00
|
|
|
sub handleMessage {
|
|
|
|
my $connection = shift;
|
|
|
|
my $message = shift;
|
|
|
|
|
2020-04-04 23:08:19 +08:00
|
|
|
# CUA - Axis camera send the message quoted with"
|
|
|
|
# CUA - Also Axis camera cannot save the plus sign which
|
|
|
|
$message =~ s/^\"//g;
|
|
|
|
$message =~ s/\"$//g;
|
|
|
|
|
2017-01-11 04:29:54 +08:00
|
|
|
my ( $id, $action, $score, $cause, $text, $showtext )
|
|
|
|
= split( /\|/, $message );
|
2020-04-04 23:08:19 +08:00
|
|
|
$score = 0 if !defined($score);
|
|
|
|
$cause = '' if !defined($cause);
|
|
|
|
$text = '' if !defined($text);
|
2017-01-11 04:29:54 +08:00
|
|
|
|
|
|
|
my $monitor = $monitors{$id};
|
|
|
|
if ( !$monitor ) {
|
2020-11-13 23:40:55 +08:00
|
|
|
loadMonitors();
|
|
|
|
$monitor = $monitors{$id};
|
|
|
|
if ( !$monitor ) {
|
|
|
|
Warning("Can't find monitor '$id' for message '$message'");
|
|
|
|
return;
|
|
|
|
}
|
2017-01-11 04:29:54 +08:00
|
|
|
}
|
2020-11-12 06:40:31 +08:00
|
|
|
if ( !zmMemVerify($monitor) ) {
|
|
|
|
Warning("Can't verify monitor '$id' for message '$message'");
|
|
|
|
return;
|
|
|
|
}
|
2017-01-11 04:29:54 +08:00
|
|
|
|
2018-12-21 02:56:57 +08:00
|
|
|
Debug("Handling action '$action'");
|
2020-04-04 23:08:19 +08:00
|
|
|
if ( $action =~ /^(enable|disable)(?:[\+ ](\d+))?$/ ) {
|
2017-01-11 04:29:54 +08:00
|
|
|
my $state = $1;
|
|
|
|
my $delay = $2;
|
2017-01-13 01:54:40 +08:00
|
|
|
if ( $state eq 'enable' ) {
|
2018-12-21 02:56:57 +08:00
|
|
|
zmMonitorEnable($monitor);
|
2017-01-11 04:29:54 +08:00
|
|
|
} else {
|
2018-12-21 02:56:57 +08:00
|
|
|
zmMonitorDisable($monitor);
|
2015-04-16 13:05:47 +08:00
|
|
|
}
|
2017-01-11 04:29:54 +08:00
|
|
|
# Force a reload
|
|
|
|
$monitor_reload_time = 0;
|
2020-11-12 05:12:30 +08:00
|
|
|
Info('Set monitor to '.$state);
|
2017-01-11 04:29:54 +08:00
|
|
|
if ( $delay ) {
|
2020-11-12 05:12:30 +08:00
|
|
|
my $action_text = $id.'|'.(($state eq 'enable') ? 'disable' : 'enable');
|
2017-01-11 04:29:54 +08:00
|
|
|
handleDelay($delay, $connection, $action_text);
|
2015-04-16 13:05:47 +08:00
|
|
|
}
|
2017-01-11 04:29:54 +08:00
|
|
|
} elsif ( $action =~ /^(on|off)(?:[ \+](\d+))?$/ ) {
|
2020-11-12 06:40:31 +08:00
|
|
|
if ( !$monitor->{Enabled} ) {
|
|
|
|
Info('Motion detection not enabled on monitor '.$id);
|
|
|
|
return;
|
|
|
|
}
|
2017-01-11 04:29:54 +08:00
|
|
|
|
|
|
|
my $trigger = $1;
|
|
|
|
my $delay = $2;
|
|
|
|
my $trigger_data;
|
2017-01-13 01:54:40 +08:00
|
|
|
if ( $trigger eq 'on' ) {
|
2020-11-12 06:40:31 +08:00
|
|
|
if ( $score <= 0 ) {
|
|
|
|
Warning('Triggering on with invalid score will have no result.');
|
|
|
|
return;
|
|
|
|
}
|
2018-12-21 02:56:57 +08:00
|
|
|
zmTriggerEventOn($monitor, $score, $cause, $text);
|
|
|
|
zmTriggerShowtext($monitor, $showtext) if defined($showtext);
|
|
|
|
Info("Trigger '$trigger' '$cause'");
|
2017-01-11 04:29:54 +08:00
|
|
|
if ( $delay ) {
|
2017-01-13 01:54:40 +08:00
|
|
|
my $action_text = $id.'|cancel';
|
2017-01-11 04:29:54 +08:00
|
|
|
handleDelay($delay, $connection, $action_text);
|
|
|
|
}
|
2017-01-13 01:54:40 +08:00
|
|
|
} elsif ( $trigger eq 'off' ) {
|
2017-01-11 04:29:54 +08:00
|
|
|
if ( $delay ) {
|
2017-01-13 01:54:40 +08:00
|
|
|
my $action_text = $id.'|off|0|'.$cause.'|'.$text;
|
2017-01-11 04:29:54 +08:00
|
|
|
handleDelay($delay, $connection, $action_text);
|
|
|
|
} else {
|
2018-12-21 02:56:57 +08:00
|
|
|
my $last_event = zmGetLastEvent($monitor);
|
|
|
|
zmTriggerEventOff($monitor);
|
|
|
|
zmTriggerShowtext($monitor, $showtext) if defined($showtext);
|
|
|
|
Info("Trigger '$trigger'");
|
2017-01-11 04:29:54 +08:00
|
|
|
# Wait til it's finished
|
2019-08-09 02:11:24 +08:00
|
|
|
while ( zmInAlarm($monitor)
|
2018-12-21 02:56:57 +08:00
|
|
|
&& ($last_event == zmGetLastEvent($monitor))
|
2017-07-14 22:28:31 +08:00
|
|
|
) {
|
2017-01-11 04:29:54 +08:00
|
|
|
# Tenth of a second
|
2018-12-21 02:56:57 +08:00
|
|
|
usleep(100000);
|
2015-04-16 13:05:47 +08:00
|
|
|
}
|
2018-12-21 02:56:57 +08:00
|
|
|
zmTriggerEventCancel($monitor);
|
2019-08-09 02:11:24 +08:00
|
|
|
} # end if delay or not
|
2017-01-11 04:29:54 +08:00
|
|
|
} # end if trigger is on or off
|
2019-08-09 02:11:24 +08:00
|
|
|
} elsif ( $action eq 'cancel' ) {
|
2018-12-21 02:56:57 +08:00
|
|
|
zmTriggerEventCancel($monitor);
|
|
|
|
zmTriggerShowtext($monitor, $showtext) if defined($showtext);
|
|
|
|
Info('Cancelled event');
|
2019-08-09 02:11:24 +08:00
|
|
|
} elsif ( $action eq 'show' ) {
|
2017-01-11 04:29:54 +08:00
|
|
|
zmTriggerShowtext( $monitor, $showtext );
|
2018-12-21 02:56:57 +08:00
|
|
|
Info("Updated show text to '$showtext'");
|
2017-01-11 04:29:54 +08:00
|
|
|
} else {
|
2018-12-21 02:56:57 +08:00
|
|
|
Error("Unrecognised action '$action' in message '$message'");
|
2017-01-11 04:29:54 +08:00
|
|
|
}
|
2014-01-04 01:55:30 +08:00
|
|
|
} # end sub handleMessage
|
|
|
|
|
2017-01-11 04:29:54 +08:00
|
|
|
sub handleDelay {
|
|
|
|
my $delay = shift;
|
|
|
|
my $connection = shift;
|
|
|
|
my $action_text = shift;
|
|
|
|
|
|
|
|
my $action_time = time()+$delay;
|
2019-08-09 02:38:27 +08:00
|
|
|
|
|
|
|
# Need to check and cancel previous actions. See issue #2619
|
|
|
|
foreach my $a_time ( keys %actions ) {
|
|
|
|
if ( $a_time <= $action_time ) {
|
|
|
|
for ( my $i = 0; $i < @{$actions{$a_time}}; $i ++ ) {
|
|
|
|
my $action = $actions{$a_time}[$i];
|
|
|
|
if ( $$action{message} eq $action_text ) {
|
|
|
|
Info("Found duplicate action '$$action{message}' at $a_time, cancelling it");
|
|
|
|
splice @{$actions{$a_time}}, $i, 1;
|
|
|
|
}
|
|
|
|
} # end foreach action
|
|
|
|
delete $actions{$a_time} if !@{$actions{$a_time}};
|
|
|
|
} # end if
|
|
|
|
} # end foreach action_time
|
|
|
|
|
2017-01-11 04:29:54 +08:00
|
|
|
my $action_array = $actions{$action_time};
|
|
|
|
if ( !$action_array ) {
|
|
|
|
$action_array = $actions{$action_time} = [];
|
|
|
|
}
|
2019-08-09 02:11:24 +08:00
|
|
|
push @$action_array, { connection=>$connection, message=>$action_text };
|
2018-12-21 02:56:57 +08:00
|
|
|
Debug("Added timed event '$action_text', expires at $action_time (+$delay secs)");
|
2016-01-16 10:31:31 +08:00
|
|
|
}
|
2018-12-21 02:56:57 +08:00
|
|
|
|
2014-01-04 01:55:30 +08:00
|
|
|
1;
|
|
|
|
__END__
|
2017-07-14 22:28:31 +08:00
|
|
|
|
|
|
|
=head1 NAME
|
|
|
|
|
|
|
|
zmtrigger.pl - ZoneMinder External Trigger Script
|
|
|
|
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
|
|
|
|
This script is used to trigger and cancel alarms from external connections
|
|
|
|
using an arbitrary text based format.
|
|
|
|
|
|
|
|
This script offers generic solution to external triggering of alarms. It
|
|
|
|
can handle external connections via either internet socket, unix socket or
|
|
|
|
file/device interfaces. You can either use it 'as is' if you can interface
|
|
|
|
with the existing format, or override connections and channels to customise
|
|
|
|
it to your needs.
|
|
|
|
|
|
|
|
If enabled by the OPT_TRIGGERS option, Zoneminder service start
|
|
|
|
zmtrigger.pl which listens for control messages on TCP port 6802.
|
|
|
|
|
|
|
|
=head1 TRIGGER MESSAGE FORMAT
|
|
|
|
|
|
|
|
B<id>|B<action>|B<score>|B<cause>|B<text>|B<showtext>
|
|
|
|
|
|
|
|
=over 4
|
|
|
|
|
|
|
|
=item B<id>
|
|
|
|
|
|
|
|
is the id number or name of the ZM monitor.
|
|
|
|
|
|
|
|
=item B<action>
|
|
|
|
|
|
|
|
Valid actions are 'on', 'off', 'cancel' or 'show' where
|
|
|
|
'on' forces an alarm condition on;
|
|
|
|
'off' forces an alarm condition off;
|
|
|
|
'cancel' negates the previous 'on' or 'off';
|
|
|
|
'show' updates the auxiliary text represented by the %Q
|
|
|
|
placeholder, which can optionally be added to the affected monitor's
|
|
|
|
timestamp label format.
|
|
|
|
|
|
|
|
Ordinarily you would use 'on' and 'cancel', 'off' would tend to be
|
|
|
|
used to suppress motion based events. Additionally 'on' and 'off' can
|
|
|
|
take an additional time offset, e.g. on+20 which automatically
|
|
|
|
cancel's the previous action after that number of seconds.
|
|
|
|
|
|
|
|
=item B<score>
|
|
|
|
|
|
|
|
is the score given to the alarm, usually to indicate it's
|
|
|
|
importance. For 'on' triggers it should be non-zero, otherwise it should
|
|
|
|
be zero.
|
|
|
|
|
|
|
|
=item B<cause>
|
|
|
|
|
|
|
|
is a 32 char max string indicating the reason for, or source of
|
|
|
|
the alarm e.g. 'Relay 1 open'. This is saved in the 'Cause' field of the
|
|
|
|
event. Ignored for 'off' or 'cancel' messages.
|
|
|
|
|
|
|
|
=item B<text>
|
|
|
|
|
|
|
|
is a 256 char max additional info field, which is saved in the
|
|
|
|
'Description' field of an event. Ignored for 'off' or 'cancel' messages.
|
|
|
|
|
|
|
|
=item B<showtext>
|
|
|
|
|
|
|
|
is up to 32 characters of text that can be displayed in the
|
|
|
|
timestamp that is added to images. The 'show' action is designed to
|
|
|
|
update this text without affecting alarms but the text is updated, if
|
|
|
|
present, for any of the actions. This is designed to allow external input
|
|
|
|
to appear on the images captured, for instance temperature or personnel
|
|
|
|
identity etc.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
2018-12-21 02:56:57 +08:00
|
|
|
Note that multiple messages can be sent at once and should be LF or CRLF
|
|
|
|
delimited. This script is not necessarily intended to be a solution in
|
|
|
|
itself, but is intended to be used as 'glue' to help ZoneMinder interface
|
|
|
|
with other systems. It will almost certainly require some customisation
|
|
|
|
before you can make any use of it. If all you want to do is generate alarms
|
|
|
|
from external sources then using the ZoneMinder::SharedMem perl module is
|
|
|
|
likely to be easier.
|
2017-07-14 22:28:31 +08:00
|
|
|
|
|
|
|
=head1 EXAMPLES
|
|
|
|
|
|
|
|
3|on+10|1|motion|text|showtext
|
|
|
|
|
|
|
|
Triggers 'alarm' on camera #3 for 10 seconds with score=1, cause='motion'.
|
|
|
|
|
|
|
|
=cut
|