Files
zoneminder/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm
2018-03-09 09:19:26 -05:00

390 lines
12 KiB
Perl

# ==========================================================================
#
# ZoneMinder HikVision Control Protocol Module
# Copyright (C) 2016 Terry Sanders
#
# 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
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ==========================================================================
#
# This module contains an implementation of the HikVision ISAPI camera control
# protocol
#
package ZoneMinder::Control::HikVision;
use 5.006;
use strict;
use warnings;
require ZoneMinder::Base;
require ZoneMinder::Control;
our @ISA = qw(ZoneMinder::Control);
# ==========================================================================
#
# HiKVision ISAPI Control Protocol
#
# Set the following:
# ControlAddress: username:password@camera_webaddress:port
# ControlDevice: IP Camera Model
#
# ==========================================================================
use ZoneMinder::Logger qw(:all);
use Time::HiRes qw( usleep );
use LWP::UserAgent;
use HTTP::Cookies;
my $ChannelID = 1; # Usually...
my $DefaultFocusSpeed = 50; # Should be between 1 and 100
my $DefaultIrisSpeed = 50; # Should be between 1 and 100
sub open {
my $self = shift;
$self->loadMonitor();
#
# Create a UserAgent for the requests
#
$self->{UA} = LWP::UserAgent->new();
$self->{UA}->cookie_jar( {} );
#
# Extract the username/password host/port from ControlAddress
#
my ($user,$pass,$host,$port);
if( $self->{Monitor}{ControlAddress} =~ /^([^:]+):([^@]+)@(.+)/ ) { # user:pass@host...
$user = $1;
$pass = $2;
$host = $3;
}
elsif( $self->{Monitor}{ControlAddress} =~ /^([^@]+)@(.+)/ ) { # user@host...
$user = $1;
$host = $2;
}
else { # Just a host
$host = $self->{Monitor}{ControlAddress};
}
# Check if it is a host and port or just a host
if( $host =~ /([^:]+):(.+)/ ) {
$host = $1;
$port = $2;
}
else {
$port = 80;
}
# Save the credentials
if( defined($user) ) {
$self->{UA}->credentials( "$host:$port", $self->{Monitor}{ControlDevice}, $user, $pass );
}
# Save the base url
$self->{BaseURL} = "http://$host:$port";
}
sub PutCmd {
my $self = shift;
my $cmd = shift;
my $content = shift;
my $req = HTTP::Request->new(PUT => "$self->{BaseURL}/$cmd");
if(defined($content)) {
$req->content_type("application/x-www-form-urlencoded; charset=UTF-8");
$req->content('<?xml version="1.0" encoding="UTF-8"?>' . "\n" . $content);
}
my $res = $self->{UA}->request($req);
unless( $res->is_success ) {
#
# The camera timeouts connections at short intervals. When this
# happens the user agent connects again and uses the same auth tokens.
# The camera rejects this and asks for another token but the UserAgent
# just gives up. Because of this I try the request again and it should
# succeed the second time if the credentials are correct.
#
if($res->code == 401) {
$res = $self->{UA}->request($req);
unless( $res->is_success ) {
#
# It has failed authentication. The odds are
# that the user has set some paramater incorrectly
# so check the realm against the ControlDevice
# entry and send a message if different
#
my $auth = $res->headers->www_authenticate;
foreach (split(/\s*,\s*/,$auth)) {
if( $_ =~ /^realm\s*=\s*"([^"]+)"/i ) {
if( $self->{Monitor}{ControlDevice} ne $1 ) {
Info "Control Device appears to be incorrect.";
Info "Control Device should be set to \"$1\".";
Info "Control Device currently set to \"$self->{Monitor}{ControlDevice}\".";
}
}
}
#
# Check for username/password
#
if( $self->{Monitor}{ControlAddress} =~ /.+:(.+)@.+/ ) {
Info "Check username/password is correct";
} elsif ( $self->{Monitor}{ControlAddress} =~ /^[^:]+@.+/ ) {
Info "No password in Control Address. Should there be one?";
} elsif ( $self->{Monitor}{ControlAddress} =~ /^:.+@.+/ ) {
Info "Password but no username in Control Address.";
} else {
Info "Missing username and password in Control Address.";
}
Fatal $res->status_line;
}
}
else {
Fatal $res->status_line;
}
}
}
#
# The move continuous functions all call moveVector
# with the direction to move in. This includes zoom
#
sub moveVector {
my $self = shift;
my $pandirection = shift;
my $tiltdirection = shift;
my $zoomdirection = shift;
my $params = shift;
my $command; # The ISAPI/PTZ command
# Calculate autostop time
my $duration = $self->getParam( $params, 'autostop', 0 ) * $self->{Monitor}{AutoStopTimeout};
# Change from microseconds to milliseconds
$duration = int($duration/1000);
my $momentxml;
if( $duration ) {
$momentxml = "<Momentary><duration>$duration</duration></Momentary>";
$command = "ISAPI/PTZCtrl/channels/$ChannelID/momentary";
}
else {
$momentxml = "";
$command = "ISAPI/PTZCtrl/channels/$ChannelID/continuous";
}
# Calculate movement speeds
my $x = $pandirection * $self->getParam( $params, 'panspeed', 0 );
my $y = $tiltdirection * $self->getParam( $params, 'tiltspeed', 0 );
my $z = $zoomdirection * $self->getParam( $params, 'speed', 0 );
# Create the XML
my $xml = "<PTZData><pan>$x</pan><tilt>$y</tilt><zoom>$z</zoom>$momentxml</PTZData>";
# Send it to the camera
$self->PutCmd($command,$xml);
}
sub moveStop { $_[0]->moveVector( 0, 0, 0, splice(@_,1)); }
sub moveConUp { $_[0]->moveVector( 0, 1, 0, splice(@_,1)); }
sub moveConUpRight { $_[0]->moveVector( 1, 1, 0, splice(@_,1)); }
sub moveConRight { $_[0]->moveVector( 1, 0, 0, splice(@_,1)); }
sub moveConDownRight { $_[0]->moveVector( 1, -1, 0, splice(@_,1)); }
sub moveConDown { $_[0]->moveVector( 0, -1, 0, splice(@_,1)); }
sub moveConDownLeft { $_[0]->moveVector( -1, -1, 0, splice(@_,1)); }
sub moveConLeft { $_[0]->moveVector( -1, 0, 0, splice(@_,1)); }
sub moveConUpLeft { $_[0]->moveVector( -1, 1, 0, splice(@_,1)); }
sub zoomConTele { $_[0]->moveVector( 0, 0, 1, splice(@_,1)); }
sub zoomConWide { $_[0]->moveVector( 0, 0,-1, splice(@_,1)); }
#
# Presets including Home set and clear
#
sub presetGoto {
my $self = shift;
my $params = shift;
my $preset = $self->getParam($params,'preset');
$self->PutCmd("ISAPI/PTZCtrl/channels/$ChannelID/presets/$preset/goto");
}
sub presetSet {
my $self = shift;
my $params = shift;
my $preset = $self->getParam($params,'preset');
my $xml = "<PTZPreset><id>$preset</id></PTZPreset>";
$self->PutCmd("ISAPI/PTZCtrl/channels/$ChannelID/presets/$preset",$xml);
}
sub presetHome {
my $self = shift;
my $params = shift;
$self->PutCmd("ISAPI/PTZCtrl/channels/$ChannelID/homeposition/goto");
}
#
# Focus controls all call Focus with a +/- speed
#
sub Focus {
my $self = shift;
my $speed = shift;
my $xml = "<FocusData><focus>$speed</focus></FocusData>";
$self->PutCmd("ISAPI/System/Video/inputs/channels/$ChannelID/focus",$xml);
}
sub focusConNear {
my $self = shift;
my $params = shift;
# Calculate autostop time
my $duration = $self->getParam( $params, 'autostop', 0 ) * $self->{Monitor}{AutoStopTimeout};
# Get the focus speed
my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed );
$self->Focus(-$speed);
if($duration) {
usleep($duration);
$self->moveStop($params);
}
}
sub Near {
my $self = shift;
my $params = shift;
$self->Focus(-$DefaultFocusSpeed);
}
sub focusAbsNear {
my $self = shift;
my $params = shift;
# Get the focus speed
my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed );
$self->Focus(-$speed);
}
sub focusRelNear {
my $self = shift;
my $params = shift;
# Get the focus speed
my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed );
$self->Focus(-$speed);
}
sub focusConFar {
my $self = shift;
my $params = shift;
# Calculate autostop time
my $duration = $self->getParam( $params, 'autostop', 0 ) * $self->{Monitor}{AutoStopTimeout};
# Get the focus speed
my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed );
$self->Focus($speed);
if($duration) {
usleep($duration);
$self->moveStop($params);
}
}
sub Far {
my $self = shift;
my $params = shift;
$self->Focus($DefaultFocusSpeed);
}
sub focusAbsFar {
my $self = shift;
my $params = shift;
# Get the focus speed
my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed );
$self->Focus($speed);
}
sub focusRelFar {
my $self = shift;
my $params = shift;
# Get the focus speed
my $speed = $self->getParam( $params, 'speed', $DefaultFocusSpeed );
$self->Focus($speed);
}
#
# Iris controls all call Iris with a +/- speed
#
sub Iris {
my $self = shift;
my $speed = shift;
my $xml = "<IrisData><iris>$speed</iris></IrisData>";
$self->PutCmd("ISAPI/System/Video/inputs/channels/$ChannelID/iris",$xml);
}
sub irisConClose {
my $self = shift;
my $params = shift;
# Calculate autostop time
my $duration = $self->getParam( $params, 'autostop', 0 ) * $self->{Monitor}{AutoStopTimeout};
# Get the iris speed
my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed );
$self->Iris(-$speed);
if($duration) {
usleep($duration);
$self->moveStop($params);
}
}
sub Close {
my $self = shift;
my $params = shift;
$self->Iris(-$DefaultIrisSpeed);
}
sub irisAbsClose {
my $self = shift;
my $params = shift;
# Get the iris speed
my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed );
$self->Iris(-$speed);
}
sub irisRelClose {
my $self = shift;
my $params = shift;
# Get the iris speed
my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed );
$self->Iris(-$speed);
}
sub irisConOpen {
my $self = shift;
my $params = shift;
# Calculate autostop time
my $duration = $self->getParam( $params, 'autostop', 0 ) * $self->{Monitor}{AutoStopTimeout};
# Get the iris speed
my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed );
$self->Iris($speed);
if($duration) {
usleep($duration);
$self->moveStop($params);
}
}
sub Open {
my $self = shift;
my $params = shift;
$self->Iris($DefaultIrisSpeed);
}
sub irisAbsOpen {
my $self = shift;
my $params = shift;
# Get the iris speed
my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed );
$self->Iris($speed);
}
sub irisRelOpen {
my $self = shift;
my $params = shift;
# Get the iris speed
my $speed = $self->getParam( $params, 'speed', $DefaultIrisSpeed );
$self->Iris($speed);
}
#
# reset (reboot) the device
#
sub reset {
my $self = shift;
$self->PutCmd("ISAPI/System/reboot");
}
1;