Merge branch 'master' of github.com:ZoneMinder/ZoneMinder
This commit is contained in:
commit
06eb38f802
|
@ -39,6 +39,8 @@ sub AUTOLOAD
|
|||
my $class = ref($self) || croak( "$self not object" );
|
||||
my $name = $AUTOLOAD;
|
||||
$name =~ s/.*://;
|
||||
## This seems odd... if the method existed would we even be here?
|
||||
## https://perldoc.perl.org/perlsub.html#Autoloading
|
||||
if ( exists($self->{$name}) )
|
||||
{
|
||||
return( $self->{$name} );
|
||||
|
@ -46,9 +48,17 @@ sub AUTOLOAD
|
|||
Fatal( "Can't access $name member of object of class $class" );
|
||||
}
|
||||
|
||||
# FIXME: Do we really have to open a new connection every time?
|
||||
|
||||
#Digest usernbme="bdmin", reblm="Login to 4K05DB3PAJE98BE", nonae="1720242756",
|
||||
#uri="/agi-bin/ptz.agi?bation=getStbtus&ahbnnel=1", response="10dd925b26ebd559353734635b859b8b",
|
||||
#opbque="1a99677524b4ae63bbe3a132b2e9b38e3b163ebd", qop=buth, na=00000001, anonae="ab1bb5d43aa5d542"
|
||||
|
||||
sub open
|
||||
{
|
||||
#Debug("&open invoked by: " . (caller(1))[3]);
|
||||
my $self = shift;
|
||||
my $cgi = shift || '/cgi-bin/configManager.cgi?action=getConfig&name=Ptz';
|
||||
$self->loadMonitor();
|
||||
|
||||
# The Dahua camera firmware API supports the concept of having multiple
|
||||
|
@ -73,60 +83,55 @@ sub open
|
|||
}
|
||||
|
||||
use LWP::UserAgent;
|
||||
$self->{ua} = LWP::UserAgent->new;
|
||||
$self->{ua} = LWP::UserAgent->new(keep_alive => 1);
|
||||
$self->{ua}->agent("ZoneMinder Control Agent/".$ZoneMinder::Base::ZM_VERSION);
|
||||
$self->{state} = 'closed';
|
||||
# credentials: ("ip:port" (no prefix!), realm (string), username (string), password (string)
|
||||
Debug("sendCmd credentials control address:'".$ADDRESS
|
||||
."' realm:'" . $REALM
|
||||
. "' username:'" . $USERNAME
|
||||
. "' password:'".$PASSWORD
|
||||
."'"
|
||||
);
|
||||
$self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD);
|
||||
|
||||
# Detect REALM
|
||||
my $get_config_url = $PROTOCOL . $ADDRESS . "/cgi-bin/configManager.cgi?action=getConfig&name=Ptz";
|
||||
my $req = HTTP::Request->new(GET=>$get_config_url);
|
||||
my $url = $PROTOCOL . $ADDRESS . $cgi;
|
||||
my $req = HTTP::Request->new(GET=>$url);
|
||||
my $res = $self->{ua}->request($req);
|
||||
|
||||
if ($res->is_success) {
|
||||
$self->{state} = 'open';
|
||||
return;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ( $res->status_line() eq '401 Unauthorized' ) {
|
||||
my $headers = $res->headers();
|
||||
foreach my $k (keys %$headers) {
|
||||
Debug("Initial Header $k => $$headers{$k}");
|
||||
}
|
||||
|
||||
if ($$headers{'www-authenticate'}) {
|
||||
my ($auth, $tokens) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/;
|
||||
Debug("Tokens: " . $tokens);
|
||||
## FIXME: This is necessary because the Dahua spec does not match reality
|
||||
if ($tokens =~ /\w+="([^"]+)"/i) {
|
||||
if ($REALM ne $1) {
|
||||
$REALM = $1;
|
||||
Debug("Changing REALM to '" . $REALM . "'");
|
||||
$self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD);
|
||||
my $req = HTTP::Request->new(GET=>$get_config_url);
|
||||
my $req = HTTP::Request->new(GET=>$url);
|
||||
$res = $self->{ua}->request($req);
|
||||
|
||||
if ($res->is_success()) {
|
||||
$self->{state} = 'open';
|
||||
return;
|
||||
Debug('Authentication succeeded...');
|
||||
return 1;
|
||||
}
|
||||
Debug('Authentication still failed after updating REALM' . $res->status_line);
|
||||
$headers = $res->headers();
|
||||
foreach my $k ( keys %$headers ) {
|
||||
Debug("Initial Header $k => $$headers{$k}");
|
||||
} # end foreach
|
||||
} else {
|
||||
Error('Authentication failed, not a REALM problem');
|
||||
} else { ## NOTE: Each of these else conditions is fatal as the command will not be
|
||||
## executed. No use going further.
|
||||
Fatal('Authentication failed: Check username and password.');
|
||||
}
|
||||
} else {
|
||||
Error('Failed to match realm in tokens');
|
||||
Fatal('Authentication failed: Incorrect realm.');
|
||||
} # end if
|
||||
} else {
|
||||
Error('No WWW-Authenticate Header');
|
||||
Fatal('Authentication failed: No www-authenticate header returned.');
|
||||
} # end if headers
|
||||
} # end if $res->status_line() eq '401 Unauthorized'
|
||||
}
|
||||
|
@ -146,45 +151,40 @@ sub printMsg
|
|||
Debug( $msg."[".$msg_len."]" );
|
||||
}
|
||||
|
||||
sub sendGetRequest {
|
||||
sub _sendGetRequest {
|
||||
my $self = shift;
|
||||
my $url_path = shift;
|
||||
|
||||
my $result = undef;
|
||||
# Attempt to reuse the connection
|
||||
|
||||
# FIXME: I think we need some sort of keepalive/heartbeat sent to the camera
|
||||
# in order to keep the session alive. As it is, it appears that the
|
||||
# ua's authentication times out or some such.
|
||||
#
|
||||
# This might be of some use:
|
||||
# {"method":"global.keepAlive","params":{"timeout":300,"active":false},"id":1518,"session":"dae233a51c0693519395209b271411b6"}[!http]
|
||||
# The web browser interface POSTs commands as JSON using js
|
||||
|
||||
my $url = $PROTOCOL . $ADDRESS . $url_path;
|
||||
my $req = HTTP::Request->new(GET=>$url);
|
||||
|
||||
my $req = HTTP::Request->new(GET => $url);
|
||||
my $res = $self->{ua}->request($req);
|
||||
|
||||
if ($res->is_success) {
|
||||
$result = !undef;
|
||||
return 1;
|
||||
} else {
|
||||
if ($res->status_line() eq '401 Unauthorized') {
|
||||
Debug("Error check failed, trying again: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD);
|
||||
Debug("Content was " . $res->content() );
|
||||
my $res = $self->{ua}->request($req);
|
||||
if ($res->is_success) {
|
||||
$result = !undef;
|
||||
} else {
|
||||
Error("Content was " . $res->content() );
|
||||
}
|
||||
}
|
||||
if ( ! $result ) {
|
||||
Error("Error check failed: '".$res->status_line());
|
||||
}
|
||||
return($self->open($url_path)); # if we have to, open a new connection
|
||||
}
|
||||
return($result);
|
||||
}
|
||||
|
||||
sub sendPtzCommand
|
||||
sub _sendPtzCommand
|
||||
{
|
||||
my $self = shift;
|
||||
my $action = shift;
|
||||
my $command_code = shift;
|
||||
my $arg1 = shift;
|
||||
my $arg2 = shift;
|
||||
my $arg3 = shift;
|
||||
my $arg1 = shift || 0;
|
||||
my $arg2 = shift || 0;
|
||||
my $arg3 = shift || 0;
|
||||
my $arg4 = shift || 0;
|
||||
|
||||
my $channel = $self->{dahua_channel_number};
|
||||
|
||||
|
@ -194,10 +194,12 @@ sub sendPtzCommand
|
|||
$url_path .= "code=" . $command_code . "&";
|
||||
$url_path .= "arg1=" . $arg1 . "&";
|
||||
$url_path .= "arg2=" . $arg2 . "&";
|
||||
$url_path .= "arg3=" . $arg3;
|
||||
$self->sendGetRequest($url_path);
|
||||
$url_path .= "arg3=" . $arg3 . "&";
|
||||
$url_path .= "arg4=" . $arg4;
|
||||
return $self->_sendGetRequest($url_path);
|
||||
}
|
||||
sub sendMomentaryPtzCommand
|
||||
|
||||
sub _sendMomentaryPtzCommand
|
||||
{
|
||||
my $self = shift;
|
||||
my $command_code = shift;
|
||||
|
@ -206,92 +208,195 @@ sub sendMomentaryPtzCommand
|
|||
my $arg3 = shift;
|
||||
my $duration_ms = shift;
|
||||
|
||||
$self->sendPtzCommand("start", $command_code, $arg1, $arg2, $arg3);
|
||||
$self->_sendPtzCommand("start", $command_code, $arg1, $arg2, $arg3);
|
||||
my $duration_ns = $duration_ms * 1000;
|
||||
usleep($duration_ns);
|
||||
$self->sendPtzCommand("stop", $command_code, $arg1, $arg2, $arg3);
|
||||
$self->_sendPtzCommand("stop", $command_code, $arg1, $arg2, $arg3);
|
||||
}
|
||||
|
||||
sub _sendAbsolutePositionCommand
|
||||
{
|
||||
my $self = shift;
|
||||
my $arg1 = shift;
|
||||
my $arg2 = shift;
|
||||
my $arg3 = shift;
|
||||
my $arg4 = shift;
|
||||
|
||||
$self->_sendPtzCommand("start", "PositionABS", $arg1, $arg2, $arg3, $arg4);
|
||||
}
|
||||
|
||||
sub moveConLeft
|
||||
{
|
||||
my $self = shift;
|
||||
Debug("Move Up Left");
|
||||
$self->_sendMomentaryPtzCommand("Left", 0, 1, 0, 0);
|
||||
}
|
||||
|
||||
sub moveConRight
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Right" );
|
||||
$self->_sendMomentaryPtzCommand("Right", 0, 1, 0, 0);
|
||||
}
|
||||
|
||||
sub moveConUp
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Up" );
|
||||
$self->_sendMomentaryPtzCommand("Up", 0, 1, 0, 0);
|
||||
}
|
||||
|
||||
sub moveConDown
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Down" );
|
||||
$self->_sendMomentaryPtzCommand("Down", 0, 1, 0, 0);
|
||||
}
|
||||
|
||||
sub moveConUpRight
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Diagonally Up Right" );
|
||||
$self->_sendMomentaryPtzCommand("RightUp", 1, 1, 0, 0);
|
||||
}
|
||||
|
||||
sub moveConDownRight
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Diagonally Down Right" );
|
||||
$self->_sendMomentaryPtzCommand("RightDown", 1, 1, 0, 0);
|
||||
}
|
||||
|
||||
sub moveConUpLeft
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Diagonally Up Left" );
|
||||
$self->_sendMomentaryPtzCommand("LeftUp", 1, 1, 0, 0);
|
||||
}
|
||||
|
||||
sub moveConDownLeft
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Diagonally Up Right" );
|
||||
$self->_sendMomentaryPtzCommand("LeftDown", 1, 1, 0, 0);
|
||||
}
|
||||
|
||||
sub zoomConTele
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Zoom Tele" );
|
||||
$self->_sendMomentaryPtzCommand("ZoomTele", 0, 1, 0, 0);
|
||||
}
|
||||
|
||||
sub zoomConWide
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Zoom Wide" );
|
||||
$self->_sendMomentaryPtzCommand("ZoomWide", 0, 1, 0, 0);
|
||||
}
|
||||
|
||||
sub moveRelUpLeft
|
||||
{
|
||||
my $self = shift;
|
||||
Debug("Move Up Left");
|
||||
$self->sendMomentaryPtzCommand("LeftUp", 4, 4, 0, 500);
|
||||
$self->_sendMomentaryPtzCommand("LeftUp", 4, 4, 0, 500);
|
||||
}
|
||||
|
||||
sub moveRelUp
|
||||
{
|
||||
my $self = shift;
|
||||
Debug("Move Up");
|
||||
$self->sendMomentaryPtzCommand("Up", 0, 4, 0, 500);
|
||||
$self->_sendMomentaryPtzCommand("Up", 0, 4, 0, 500);
|
||||
}
|
||||
|
||||
sub moveRelUpRight
|
||||
{
|
||||
my $self = shift;
|
||||
Debug("Move Up Right");
|
||||
$self->sendMomentaryPtzCommand("RightUp", 0, 4, 0, 500);
|
||||
$self->_sendMomentaryPtzCommand("RightUp", 0, 4, 0, 500);
|
||||
}
|
||||
|
||||
sub moveRelLeft
|
||||
{
|
||||
my $self = shift;
|
||||
Debug("Move Left");
|
||||
$self->sendMomentaryPtzCommand("Left", 0, 4, 0, 500);
|
||||
$self->_sendMomentaryPtzCommand("Left", 0, 4, 0, 500);
|
||||
}
|
||||
|
||||
sub moveRelRight
|
||||
{
|
||||
my $self = shift;
|
||||
Debug("Move Right");
|
||||
$self->sendMomentaryPtzCommand("Right", 0, 4, 0, 500);
|
||||
$self->_sendMomentaryPtzCommand("Right", 0, 4, 0, 500);
|
||||
}
|
||||
|
||||
sub moveRelDownLeft
|
||||
{
|
||||
my $self = shift;
|
||||
Debug("Move Down Left");
|
||||
$self->sendMomentaryPtzCommand("LeftDown", 4, 4, 0, 500);
|
||||
$self->_sendMomentaryPtzCommand("LeftDown", 4, 4, 0, 500);
|
||||
}
|
||||
|
||||
sub moveRelDown
|
||||
{
|
||||
my $self = shift;
|
||||
Debug("Move Down");
|
||||
$self->sendMomentaryPtzCommand("Down", 0, 4, 0, 500);
|
||||
$self->_sendMomentaryPtzCommand("Down", 0, 4, 0, 500);
|
||||
}
|
||||
|
||||
sub moveRelDownRight
|
||||
{
|
||||
my $self = shift;
|
||||
Debug("Move Down Right");
|
||||
$self->sendMomentaryPtzCommand("RightDown", 4, 4, 0, 500);
|
||||
$self->_sendMomentaryPtzCommand("RightDown", 4, 4, 0, 500);
|
||||
}
|
||||
|
||||
sub zoomRelTele
|
||||
{
|
||||
my $self = shift;
|
||||
Debug("Zoom Relative Tele");
|
||||
$self->sendMomentaryPtzCommand("ZoomTele", 0, 0, 0, 500);
|
||||
$self->_sendMomentaryPtzCommand("ZoomTele", 0, 0, 0, 500);
|
||||
}
|
||||
|
||||
sub zoomRelWide
|
||||
{
|
||||
my $self = shift;
|
||||
Debug("Zoom Relative Wide");
|
||||
$self->sendMomentaryPtzCommand("ZoomWide", 0, 0, 0, 500);
|
||||
$self->_sendMomentaryPtzCommand("ZoomWide", 0, 0, 0, 500);
|
||||
}
|
||||
|
||||
sub focusRelNear
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
my $response = $self->_sendPtzCommand("start", "FocusNear", 0, 1, 0, 0);
|
||||
Debug("focusRelNear response: " . $response);
|
||||
}
|
||||
|
||||
sub focusRelFar
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
my $response = $self->_sendPtzCommand("start", "FocusFar", 0, 1, 0, 0);
|
||||
Debug("focusRelFar response: " . $response);
|
||||
}
|
||||
|
||||
sub moveStop
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Move Stop" );
|
||||
# The command does not matter here, just the stop...
|
||||
$self->_sendPtzCommand("stop", "Up", 0, 0, 1, 0);
|
||||
}
|
||||
|
||||
sub presetClear
|
||||
{
|
||||
my $self = shift;
|
||||
my $params = shift;
|
||||
my $preset_id = $self->getParam($params, 'preset');
|
||||
$self->sendPtzCommand("start", "ClearPreset", 0, $preset_id, 0);
|
||||
$self->_sendPtzCommand("start", "ClearPreset", 0, $preset_id, 0);
|
||||
}
|
||||
|
||||
|
||||
sub presetSet
|
||||
{
|
||||
my $self = shift;
|
||||
|
@ -308,8 +413,8 @@ sub presetSet
|
|||
my $control_preset_row = $sth->fetchrow_hashref();
|
||||
my $new_label_name = $control_preset_row->{'Label'};
|
||||
|
||||
$self->sendPtzCommand("start", "SetPreset", 0, $preset_id, 0);
|
||||
$self->sendPtzCommand("start", "SetPresetName", $preset_id, $new_label_name, 0);
|
||||
$self->_sendPtzCommand("start", "SetPreset", 0, $preset_id, 0);
|
||||
$self->_sendPtzCommand("start", "SetPresetName", $preset_id, $new_label_name, 0);
|
||||
}
|
||||
|
||||
sub presetGoto
|
||||
|
@ -318,12 +423,39 @@ sub presetGoto
|
|||
my $params = shift;
|
||||
my $preset_id = $self->getParam($params, 'preset');
|
||||
|
||||
$self->sendPtzCommand("start", "GotoPreset", 0, $preset_id, 0);
|
||||
$self->_sendPtzCommand("start", "GotoPreset", 0, $preset_id, 0);
|
||||
}
|
||||
|
||||
sub presetHome
|
||||
{
|
||||
my $self = shift;
|
||||
|
||||
$self->_sendAbsolutePositionCommand( 0, 0, 0, 1 );
|
||||
}
|
||||
|
||||
sub reset
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Camera Reset" );
|
||||
$self->_sendPtzCommand("Reset", 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
sub reboot
|
||||
{
|
||||
my $self = shift;
|
||||
Debug( "Camera Reboot" );
|
||||
my $cmd = "cgi-bin/magicBox.cgi?action=reboot";
|
||||
$self->_sendGetRequest($cmd);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=pod
|
||||
|
||||
=encoding utf8
|
||||
|
||||
=head1 NAME
|
||||
|
||||
ZoneMinder::Control::Dahua - Perl module for Dahua cameras
|
||||
|
@ -337,10 +469,6 @@ place this in /usr/share/perl5/ZoneMinder/Control
|
|||
|
||||
This module is an implementation of the Dahua IP camera HTTP control API.
|
||||
|
||||
=head2 EXPORT
|
||||
|
||||
None by default.
|
||||
|
||||
=head1 COPYRIGHT AND LICENSE
|
||||
|
||||
Copyright (C) 2018 ZoneMinder LLC
|
||||
|
@ -359,4 +487,138 @@ You should have received a copy of the GNU General Public License
|
|||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
=head1 Private Methods
|
||||
|
||||
Methods intended for use internally but documented here for future developers.
|
||||
|
||||
=head2 _sendAbsolutePositionCommand( $arg1, $arg2, $arg3, $arg4 )
|
||||
|
||||
Where:
|
||||
|
||||
$arg1 = Horizontal angle 0° to 360°
|
||||
$arg2 = Vertical angle 0° to -90°
|
||||
$arg3 = Zoom multiplier
|
||||
$arg4 = Speed 1 to 8
|
||||
|
||||
This is an private method used to send an absolute position command to the
|
||||
camera.
|
||||
|
||||
=head1 Public Methods
|
||||
|
||||
Methods made available to control.pl via ZoneMinder::Control
|
||||
|
||||
=head2 Notes:
|
||||
|
||||
=over 1
|
||||
|
||||
Which methods are invoked depends on which types of movement are selected in
|
||||
the camera control type. For example: if the 'Can Move Continuous' option is
|
||||
checked, then methods including 'Con' in their names are invoked. Likewise if
|
||||
the 'Can Move Relative" option is checked, then methods including 'Rel' in
|
||||
their names are invoked.
|
||||
|
||||
|
||||
At present, these types of movement are prioritized and exclusive. This applies
|
||||
to all types of movement, not just PTZ, but focus, iris, etc. as well. The options
|
||||
are tested in the following order:
|
||||
|
||||
1. Continuous
|
||||
|
||||
2. Relative
|
||||
|
||||
3. Absolute
|
||||
|
||||
These types are exclusive meaning that the first one that matches is the one
|
||||
ZoneMinder will use to control with. It would be nice to allow the user to
|
||||
select the type used given that some cameras support all three types of
|
||||
movement.
|
||||
|
||||
=back
|
||||
|
||||
=head2 new
|
||||
|
||||
This method instantiates a new control object based upon this control module
|
||||
and sets the 'id' attribute to the value passed in.
|
||||
|
||||
=head2 open
|
||||
|
||||
This method opens an HTTP connection to the camera. It handles authentication,
|
||||
etc. Upon success it sets the 'state' attribute to 'open.'
|
||||
|
||||
=head2 close
|
||||
|
||||
This method effectively closes the HTTP connection to the camera. It sets the
|
||||
'state' attribute to 'close.'
|
||||
|
||||
=head2 printMsg
|
||||
|
||||
This method appears to be used for debugging.
|
||||
|
||||
=head2 moveCon<direction>
|
||||
|
||||
This set of methods invoke continuous movement in the direction indicated by
|
||||
the <direction> portion of their name. They accept no arguments and move the
|
||||
camera at a speed of 1 for 0ms. The speed index of 1 is the lowest of the
|
||||
accepted range of 1-8.
|
||||
|
||||
NOTE:
|
||||
|
||||
This is not true continuous movmement as currently implemented.
|
||||
|
||||
=head2 focusCon<range>
|
||||
|
||||
This set of methods invoke continuous focus in the range direction indicated
|
||||
by the <range> portion of their name. They accept no arguments.
|
||||
|
||||
NOTE:
|
||||
|
||||
This is not true continuous movmement as currently implemented.
|
||||
|
||||
=head2 moveRel<direction>
|
||||
|
||||
This set of methods invoke relatvie movement in the direction indicated by
|
||||
the <direction> portion of their name. They accept no arguments and move the
|
||||
camera at a speed of 4 for 500ms. The speed index of 4 is half-way between
|
||||
the accepted range of 1-8.
|
||||
|
||||
=head2 focusRel<range>
|
||||
|
||||
This set of methods invoke realtive focus in the range direction indicated by
|
||||
the <range> portion of their name. They accept no arguments.
|
||||
|
||||
NOTE:
|
||||
|
||||
This only just does work. The Dahua API specifies "multiples" as the input.
|
||||
We pass in a 1 for that as it does not seem to matter what number (0-8) is
|
||||
provided, the camera focus behaves the same.
|
||||
|
||||
=head2 moveStop
|
||||
|
||||
This method attempts to stop the camera. The problem is that if continuous
|
||||
motion is occurring in multiple directions, this will only stop the motion
|
||||
in the 'Up' direction. Dahua does not support an "all-stop" command.
|
||||
|
||||
=head2 presetHome
|
||||
|
||||
This method "homes" the camera to a preset position. It accepts no arguments.
|
||||
When either continuous or relative movement is enabled, pressing the center
|
||||
button on the movement controls invokes this method.
|
||||
|
||||
NOTE:
|
||||
|
||||
The Dahua protocol does not appear to support a preset Home feature. We could
|
||||
allow the user to assign a preset slot as the "home" slot. Dahua does appear
|
||||
to support naming presets which may lend itself to this sort of thing. At
|
||||
this point, we'll just send the camera back to center and zoom wide. (0°,0°,0)
|
||||
|
||||
=head2 reset
|
||||
|
||||
This method will reset the PTZ controls to their "default." It is not clear
|
||||
what that is.
|
||||
|
||||
=head2 reboot
|
||||
|
||||
This method performs a reboot of the camera. This will take the camera offline
|
||||
for the time it takes to reboot.
|
||||
|
||||
=cut
|
||||
|
|
|
@ -1,14 +1,88 @@
|
|||
<?php
|
||||
App::uses('Component', 'Controller');
|
||||
|
||||
class FilterComponent extends Component {
|
||||
|
||||
/**
|
||||
* Valid MySQL operands that can be used from namedParams with two operands
|
||||
* where the right-hand side (RHS) is a literal.
|
||||
* These came from https://dev.mysql.com/doc/refman/8.0/en/non-typed-operators.html
|
||||
*/
|
||||
public $twoOperandSQLOperands = array(
|
||||
'AND',
|
||||
'&&',
|
||||
'=',
|
||||
//':=',
|
||||
//'BETWEEN ... AND ...',
|
||||
//'BINARY',
|
||||
'&',
|
||||
'~',
|
||||
//'|',
|
||||
'^',
|
||||
//'CASE',
|
||||
'DIV',
|
||||
'/',
|
||||
'=',
|
||||
'<=>',
|
||||
'>',
|
||||
'>=',
|
||||
'IS',
|
||||
'IS NOT',
|
||||
//'IS NOT NULL',
|
||||
//'IS NULL',
|
||||
//'->',
|
||||
//'->>',
|
||||
'<<',
|
||||
'<',
|
||||
'<=',
|
||||
'LIKE',
|
||||
'-',
|
||||
'%',
|
||||
'MOD',
|
||||
//'NOT',
|
||||
//'!',
|
||||
//'NOT BETWEEN ... AND ...',
|
||||
'!=',
|
||||
'<>',
|
||||
'NOT LIKE',
|
||||
'NOT REGEXP',
|
||||
// `or` operators aren't safe as they can
|
||||
// be used to skip an existing condition
|
||||
// enforcing access to only certain
|
||||
// monitors/events.
|
||||
//'||',
|
||||
//'OR',
|
||||
'+',
|
||||
'REGEXP',
|
||||
'>>',
|
||||
'RLIKE',
|
||||
'SOUNDS LIKE',
|
||||
//'*',
|
||||
'-',
|
||||
//'XOR',
|
||||
);
|
||||
|
||||
// Build a CakePHP find() condition based on the named parameters
|
||||
// that are passed in
|
||||
public function buildFilter($namedParams) {
|
||||
if ($namedParams) {
|
||||
$conditions = array();
|
||||
|
||||
$conditions = array();
|
||||
if ($namedParams) {
|
||||
foreach ($namedParams as $attribute => $value) {
|
||||
// We need to sanitize $attribute to avoid SQL injection.
|
||||
$lhs = trim($attribute);
|
||||
$matches = NULL;
|
||||
if (preg_match('/^(?P<field>[a-z0-9]+)(?P<operator>.+)?$/i', $lhs, $matches) !== 1) {
|
||||
throw new Exception('Invalid argument before `:`: ' . $lhs);
|
||||
}
|
||||
$operator = trim($matches['operator']);
|
||||
|
||||
// Only allow operators on our allow list. No operator
|
||||
// specified defaults to `=` by cakePHP.
|
||||
if ($operator != '' && !in_array($operator, $this->twoOperandSQLOperands)) {
|
||||
throw new Exception('Invalid operator: ' . $operator);
|
||||
}
|
||||
|
||||
$lhs = '`' . $matches['field'] . '` ' . $operator;
|
||||
// If the named param contains an array, we want to turn it into an IN condition
|
||||
// Otherwise, we add it right into the $conditions array
|
||||
if (is_array($value)) {
|
||||
|
@ -18,10 +92,10 @@ class FilterComponent extends Component {
|
|||
array_push($array, $term);
|
||||
}
|
||||
|
||||
$query = array($attribute => $array);
|
||||
$query = array($lhs => $array);
|
||||
array_push($conditions, $query);
|
||||
} else {
|
||||
$conditions[$attribute] = $value;
|
||||
$conditions[$lhs] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -44,9 +44,8 @@ class EventsController extends AppController {
|
|||
}
|
||||
|
||||
if ( $this->request->params['named'] ) {
|
||||
//$this->FilterComponent = $this->Components->load('Filter');
|
||||
//$conditions = $this->FilterComponent->buildFilter($this->request->params['named']);
|
||||
$conditions = $this->request->params['named'];
|
||||
$this->FilterComponent = $this->Components->load('Filter');
|
||||
$conditions = $this->FilterComponent->buildFilter($this->request->params['named']);
|
||||
} else {
|
||||
$conditions = array();
|
||||
}
|
||||
|
@ -234,18 +233,34 @@ class EventsController extends AppController {
|
|||
|
||||
public function search() {
|
||||
$this->Event->recursive = -1;
|
||||
// Unmodified conditions to pass to find()
|
||||
$find_conditions = array();
|
||||
// Conditions to be filtered by buildFilter
|
||||
$conditions = array();
|
||||
|
||||
foreach ($this->params['named'] as $param_name => $value) {
|
||||
// Transform params into mysql
|
||||
if ( preg_match('/interval/i', $value, $matches) ) {
|
||||
$condition = array("$param_name >= (date_sub(now(), $value))");
|
||||
// Transform params into conditions
|
||||
if ( preg_match('/^\s?interval\s?/i', $value) ) {
|
||||
if (preg_match('/^[a-z0-9]+$/i', $param_name) !== 1) {
|
||||
throw new Exception('Invalid field name: ' . $param_name);
|
||||
}
|
||||
$matches = NULL;
|
||||
$value = preg_replace('/^\s?interval\s?/i', '', $value);
|
||||
if (preg_match('/^(?P<expr>[ -.:0-9\']+)\s+(?P<unit>[_a-z]+)$/i', trim($value), $matches) !== 1) {
|
||||
throw new Exception('Invalid interval: ' . $value);
|
||||
}
|
||||
$expr = trim($matches['expr']);
|
||||
$unit = trim($matches['unit']);
|
||||
array_push($find_conditions, "$param_name >= DATE_SUB(NOW(), INTERVAL $expr $unit)");
|
||||
} else {
|
||||
$condition = array($param_name => $value);
|
||||
$conditions[$param_name] = $value;
|
||||
}
|
||||
array_push($conditions, $condition);
|
||||
}
|
||||
|
||||
$this->FilterComponent = $this->Components->load('Filter');
|
||||
$conditions = $this->FilterComponent->buildFilter($conditions);
|
||||
array_push($conditions, $find_conditions);
|
||||
|
||||
$results = $this->Event->find('all', array(
|
||||
'conditions' => $conditions
|
||||
));
|
||||
|
@ -261,18 +276,32 @@ class EventsController extends AppController {
|
|||
// consoleEvents/1 hour/AlarmFrames >=: 1/AlarmFrames <=: 20.json
|
||||
|
||||
public function consoleEvents($interval = null) {
|
||||
$matches = NULL;
|
||||
// https://dev.mysql.com/doc/refman/5.5/en/expressions.html#temporal-intervals
|
||||
// Examples: `'1-1' YEAR_MONTH`, `'-1 10' DAY_HOUR`, `'1.999999' SECOND_MICROSECOND`
|
||||
if (preg_match('/^(?P<expr>[ -.:0-9\']+)\s+(?P<unit>[_a-z]+)$/i', trim($interval), $matches) !== 1) {
|
||||
throw new Exception('Invalid interval: ' . $interval);
|
||||
}
|
||||
$expr = trim($matches['expr']);
|
||||
$unit = trim($matches['unit']);
|
||||
|
||||
$this->Event->recursive = -1;
|
||||
$results = array();
|
||||
$this->FilterComponent = $this->Components->load('Filter');
|
||||
$conditions = $this->FilterComponent->buildFilter($conditions);
|
||||
array_push($conditions, array("StartTime >= DATE_SUB(NOW(), INTERVAL $expr $unit)"));
|
||||
|
||||
$moreconditions = '';
|
||||
foreach ($this->request->params['named'] as $name => $param) {
|
||||
$moreconditions = $moreconditions . ' AND '.$name.$param;
|
||||
}
|
||||
|
||||
$query = $this->Event->query("SELECT MonitorId, COUNT(*) AS Count FROM Events WHERE (StartTime >= (DATE_SUB(NOW(), interval $interval)) $moreconditions) GROUP BY MonitorId;");
|
||||
$query = $this->Event->find('all', array(
|
||||
'fields' => array(
|
||||
'MonitorId',
|
||||
'COUNT(*) AS Count',
|
||||
),
|
||||
'conditions' => $conditions,
|
||||
'group' => 'MonitorId',
|
||||
));
|
||||
|
||||
foreach ($query as $result) {
|
||||
$results[$result['Events']['MonitorId']] = $result[0]['Count'];
|
||||
$results[$result['Event']['MonitorId']] = $result[0]['Count'];
|
||||
}
|
||||
|
||||
$this->set(array(
|
||||
|
|
|
@ -40,8 +40,7 @@ class MonitorsController extends AppController {
|
|||
|
||||
if ( $this->request->params['named'] ) {
|
||||
$this->FilterComponent = $this->Components->load('Filter');
|
||||
//$conditions = $this->FilterComponent->buildFilter($this->request->params['named']);
|
||||
$conditions = $this->request->params['named'];
|
||||
$conditions = $this->FilterComponent->buildFilter($this->request->params['named']);
|
||||
} else {
|
||||
$conditions = array();
|
||||
}
|
||||
|
@ -315,6 +314,10 @@ class MonitorsController extends AppController {
|
|||
throw new NotFoundException(__('Invalid monitor'));
|
||||
}
|
||||
|
||||
if (preg_match('/^[a-z]+$/i', $daemon) !== 1) {
|
||||
throw new BadRequestException(__('Invalid command'));
|
||||
}
|
||||
|
||||
$monitor = $this->Monitor->find('first', array(
|
||||
'fields' => array('Id', 'Type', 'Device'),
|
||||
'conditions' => array('Id' => $id)
|
||||
|
|
|
@ -211,7 +211,7 @@ global $user;
|
|||
if ( ZM_OPT_USE_AUTH ) {
|
||||
$close_session = 0;
|
||||
if ( !is_session_started() ) {
|
||||
session_start();
|
||||
zm_session_start();
|
||||
$close_session = 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -26,10 +26,11 @@ function cycleNext() {
|
|||
window.location.replace('?view=cycle&mid='+monitorData[monIdx].id+'&mode='+mode, cycleRefreshTimeout);
|
||||
}
|
||||
function cyclePrev() {
|
||||
if ( monIdx )
|
||||
if (monIdx) {
|
||||
monIdx -= 1;
|
||||
else
|
||||
monIdx = monitorData.length-1;
|
||||
} else {
|
||||
monIdx = monitorData.length - 1;
|
||||
}
|
||||
|
||||
window.location.replace('?view=cycle&mid='+monitorData[monIdx].id+'&mode='+mode, cycleRefreshTimeout);
|
||||
}
|
||||
|
@ -82,9 +83,9 @@ function changeScale() {
|
|||
var scale = $('scale').get('value');
|
||||
$('width').set('value', 'auto');
|
||||
$('height').set('value', 'auto');
|
||||
Cookie.write('zmCycleScale', scale, { duration: 10*365 });
|
||||
Cookie.write('zmCycleWidth', 'auto', { duration: 10*365 });
|
||||
Cookie.write('zmCycleHeight', 'auto', { duration: 10*365 });
|
||||
Cookie.write('zmCycleScale', scale, {duration: 10*365});
|
||||
Cookie.write('zmCycleWidth', 'auto', {duration: 10*365});
|
||||
Cookie.write('zmCycleHeight', 'auto', {duration: 10*365});
|
||||
var newWidth = ( monitorData[monIdx].width * scale ) / SCALE_BASE;
|
||||
var newHeight = ( monitorData[monIdx].height * scale ) / SCALE_BASE;
|
||||
|
||||
|
|
|
@ -448,10 +448,16 @@ function cmdEnableAlarms() {
|
|||
|
||||
function cmdForceAlarm() {
|
||||
alarmCmdReq.send(alarmCmdParms+"&command=forceAlarm");
|
||||
if (window.event) {
|
||||
window.event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
function cmdCancelForcedAlarm() {
|
||||
alarmCmdReq.send(alarmCmdParms+"&command=cancelForcedAlarm");
|
||||
if (window.event) {
|
||||
window.event.preventDefault();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue