mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2025-12-23 22:37:53 -05:00
272 lines
8.4 KiB
Perl
272 lines
8.4 KiB
Perl
# ==========================================================================
|
|
#
|
|
# ZoneMinder GrandSteam Control Protocol Module
|
|
# Copyright (C) 2021 ZoneMinder Inc
|
|
#
|
|
# 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 the implementation of the Vivotek ePTZ camera control
|
|
# protocol
|
|
#
|
|
package ZoneMinder::Control::Grandstream;
|
|
|
|
use 5.006;
|
|
use strict;
|
|
use warnings;
|
|
|
|
require ZoneMinder::Base;
|
|
require ZoneMinder::Control;
|
|
|
|
our @ISA = qw(ZoneMinder::Control);
|
|
|
|
# ==========================================================================
|
|
#
|
|
# Vivotek ePTZ Control Protocol
|
|
#
|
|
# ==========================================================================
|
|
|
|
use ZoneMinder::Logger qw(:all);
|
|
use ZoneMinder::Config qw(:all);
|
|
use ZoneMinder::General qw(:all);
|
|
|
|
use Time::HiRes qw( usleep );
|
|
use URI::Encode qw(uri_encode);
|
|
use XML::LibXML;
|
|
use Digest::MD5 qw(md5 md5_hex md5_base64);
|
|
|
|
|
|
our $REALM = '';
|
|
our $PROTOCOL = 'https://';
|
|
our $USERNAME = 'admin';
|
|
our $PASSWORD = '';
|
|
our $ADDRESS = '';
|
|
our $BASE_URL = '';
|
|
|
|
my %config_types = (
|
|
upgrade => {
|
|
P6767 => { default_value=>1, desc=>'Firmware Upgrade Method http' },
|
|
P192 => { desc=>'Firmware Server Path' },
|
|
},
|
|
date => {
|
|
P64 => { desc=>'Timezone' },
|
|
P5006 => { default_value=>1, desc=>'Enable NTP' },
|
|
P30 => { desc=>'NTP Server', },
|
|
},
|
|
access => {
|
|
P12053 => {default_value=>1, desc=>'Enable UPnP Search' },
|
|
},
|
|
cmos => {
|
|
#P12314=> { value=>0, desc=>'Power Frequency' },
|
|
},
|
|
video => {
|
|
#P12306 => { value=>'26', desc=>'primary codec' },# 26: h264, 96: mjpeg, 98: h265
|
|
P12313 => { desc=>'primary profile' },# 0: baseline, 1: main, 2: high
|
|
P12307 => { desc=>'primary resolution' }, # 1025: 1920x1080 1023: 1280x960, 1022: 1280x720
|
|
P12904 => { desc=>'primary fps', }, # fps 5,10,15,20,25,30
|
|
P12311 => { desc=>'Image quality', }, # 0 very high, 4 very low
|
|
P12312 => { desc=>'Iframe interval', }, # i-frame interval 5-100
|
|
},
|
|
osd => {
|
|
P10044 => { default_value=> 1, desc=>'Display Time' },
|
|
#P10045 => { value=> 1, desc=>'Display Text' },
|
|
P10001 => { default_value=> 1, desc=>'OSD Date Format' },
|
|
#P10040 => { value=>'', desc=> 'OSD Text' },
|
|
},
|
|
audio => {
|
|
P14000 => { default_value=>1, desc=>'Audio codec' }, # 1,2
|
|
P14003 => { default_value=>0, desc=>'Audio out volume' }, # 0-6
|
|
P14002 => { default_value=>0, desc=>'Audio in volume' }, # 0-6
|
|
},
|
|
debug => {
|
|
P8042 => { default_value=>0, desc=>'Debug log protocol' }, # 0: UDP 1: SSL/TLS
|
|
P207 => { desc=>'Debug Log Server' },
|
|
P208 => { desc=>'Debug Log Level' },
|
|
},
|
|
);
|
|
|
|
sub open {
|
|
my $self = shift;
|
|
$self->loadMonitor();
|
|
|
|
if ($self->{Monitor}{ControlAddress}
|
|
and
|
|
$self->{Monitor}{ControlAddress} ne 'user:pass@ip'
|
|
and
|
|
$self->{Monitor}{ControlAddress} ne 'user:port@ip'
|
|
) {
|
|
Debug("Getting connection details from Path " . $self->{Monitor}->{ControlAddress});
|
|
if (($self->{Monitor}->{ControlAddress} =~ /^(?<PROTOCOL>https?:\/\/)?(?<USERNAME>[^:@]+)?:?(?<PASSWORD>[^\/@]+)?@?(?<ADDRESS>.*)$/)) {
|
|
$PROTOCOL = $+{PROTOCOL} if $+{PROTOCOL};
|
|
$USERNAME = $+{USERNAME} if $+{USERNAME};
|
|
$PASSWORD = $+{PASSWORD} if $+{PASSWORD};
|
|
$ADDRESS = $+{ADDRESS} if $+{ADDRESS};
|
|
}
|
|
} elsif ($self->{Monitor}->{Path}) {
|
|
Debug("Getting connection details from Path " . $self->{Monitor}->{Path});
|
|
if (($self->{Monitor}->{Path} =~ /^(?<PROTOCOL>(https?|rtsp):\/\/)?(?<USERNAME>[^:@]+)?:?(?<PASSWORD>[^\/@]+)?@?(?<ADDRESS>[^:\/]+)/)) {
|
|
$USERNAME = $+{USERNAME} if $+{USERNAME};
|
|
$PASSWORD = $+{PASSWORD} if $+{PASSWORD};
|
|
$ADDRESS = $+{ADDRESS} if $+{ADDRESS};
|
|
}
|
|
Debug("username:$USERNAME password:$PASSWORD address:$ADDRESS");
|
|
} else {
|
|
Error('Failed to parse auth from address ' . $self->{Monitor}->{ControlAddress});
|
|
$ADDRESS = $self->{Monitor}->{ControlAddress};
|
|
}
|
|
$BASE_URL = $PROTOCOL.$ADDRESS;
|
|
$$self{host} = $ADDRESS; # For use with ping
|
|
|
|
use LWP::UserAgent;
|
|
$self->{ua} = LWP::UserAgent->new;
|
|
$self->{ua}->agent('ZoneMinder Control Agent/'.ZoneMinder::Base::ZM_VERSION);
|
|
$self->{ua}->ssl_opts(verify_hostname => 0, SSL_verify_mode => 0x00);
|
|
$self->{ua}->cookie_jar( {} );
|
|
|
|
|
|
my $rescode = '';
|
|
my $url = $BASE_URL.'/goform/login?cmd=login&type=0&user='.$USERNAME;
|
|
my $response = $self->get($url);
|
|
if ($response->is_success()) {
|
|
my $dom = XML::LibXML->load_xml(string => $response->content);
|
|
my $challengeString = $dom->getElementsByTagName('ChallengeCode')->string_value();
|
|
Debug('challengstring: '.$challengeString);
|
|
my $authcode = md5_hex($challengeString.':GSC36XXlZpRsFzCbM:'.$PASSWORD);
|
|
$url .= '&authcode='.$authcode;
|
|
$response = $self->get($url);
|
|
$dom = XML::LibXML->load_xml(string => $response->content);
|
|
$rescode = $dom->getElementsByTagName('ResCode');
|
|
} else {
|
|
Warning("Falling back to old style");
|
|
$PROTOCOL = 'http://';
|
|
$BASE_URL = $PROTOCOL.$USERNAME.':'.$PASSWORD.'@'.$ADDRESS;
|
|
}
|
|
|
|
$self->{state} = 'open';
|
|
}
|
|
|
|
sub get {
|
|
my $self = shift;
|
|
my $url = shift;
|
|
Debug("Getting $url");
|
|
my $response = $self->{ua}->get($url);
|
|
Debug('Response: '. $response->status_line . ' ' . $response->content);
|
|
return $response;
|
|
}
|
|
|
|
sub close {
|
|
my $self = shift;
|
|
$self->{state} = 'closed';
|
|
}
|
|
|
|
sub sendCmd {
|
|
my ($self, $cmd, $speedcmd) = @_;
|
|
|
|
$self->printMsg( $speedcmd, 'Tx' );
|
|
$self->printMsg( $cmd, 'Tx' );
|
|
|
|
my $req = HTTP::Request->new( GET => $BASE_URL."/cgi-bin/camctrl/eCamCtrl.cgi?stream=0&$speedcmd&$cmd");
|
|
my $res = $self->{ua}->request($req);
|
|
|
|
if (!$res->is_success) {
|
|
Error('Request failed: '.$res->status_line().' (URI: '.$req->as_string().')');
|
|
}
|
|
return $res->is_success;
|
|
}
|
|
|
|
sub get_config {
|
|
my $self = shift;
|
|
|
|
my %config;
|
|
foreach my $category ( @_ ? @_ : keys %config_types ) {
|
|
my $response = $self->get($BASE_URL.'/goform/config?cmd=get&type='.$category);
|
|
my $dom = XML::LibXML->load_xml(string => $response->content);
|
|
if (!$dom) {
|
|
Error("No document from :".$response->content());
|
|
return;
|
|
}
|
|
Debug($dom->toString(1));
|
|
$config{$category} = {};
|
|
my $Configuration = $dom->getElementsByTagName('Configuration');
|
|
my $xml = $Configuration->get_node(0);
|
|
if (!$xml) {
|
|
Warning("UNable to get Configuration node from ".$response->content());
|
|
return \%config;
|
|
}
|
|
foreach my $node ($xml->childNodes()) {
|
|
$config{$category}{$node->nodeName} = {
|
|
value=>$node->textContent
|
|
};
|
|
}
|
|
} # end foreach category
|
|
return \%config;
|
|
} # end sub get_config
|
|
|
|
sub set_config {
|
|
my $self = shift;
|
|
my $updates = shift;
|
|
|
|
my $url = join('&', $BASE_URL.'/goform/config?cmd=set',
|
|
map { $_.'='.uri_encode(uri_encode($$updates{$_}{value}, { encode_reserved=>1} )) } keys %$updates );
|
|
my $response = $self->get($url);
|
|
return 0 if !$response->is_success();
|
|
return 0 if ($response->content !~ /Successful/i);
|
|
return 1;
|
|
}
|
|
|
|
sub reboot {
|
|
my $self = shift;
|
|
$self->get($BASE_URL.'/goform/config?cmd=reboot');
|
|
}
|
|
|
|
1;
|
|
__END__
|
|
|
|
=head1 NAME
|
|
|
|
ZoneMinder::Control::Grandstream - ZoneMinder Perl extension for Grandstream
|
|
camera control protocol
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
use ZoneMinder::Control::Grandstream;
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
This module implements the protocol used in various Grandstream IP cameras.
|
|
|
|
=head2 EXPORT
|
|
|
|
None.
|
|
|
|
=head1 SEE ALSO
|
|
|
|
I would say, see ZoneMinder::Control documentation. But it is a stub.
|
|
|
|
=head1 AUTHOR
|
|
|
|
Isaac Connor E<lt>isaac@zoneminder.comE<gt>
|
|
|
|
=head1 COPYRIGHT AND LICENSE
|
|
|
|
Copyright (C) 2021 by ZoneMinder Inc
|
|
|
|
This library is free software; you can redistribute it and/or modify
|
|
it under the same terms as Perl itself, either Perl version 5.8.3 or,
|
|
at your option, any later version of Perl 5 you may have available.
|
|
|
|
=cut
|