From 707444c39e87eb36f6ec3b67b03d5c3f2932125b Mon Sep 17 00:00:00 2001 From: Michael Barkdoll Date: Mon, 9 Oct 2017 09:44:45 -0500 Subject: [PATCH 1/4] Add files via upload --- .../lib/ZoneMinder/Control/TVIP672WI.pm | 590 ++++++++++++++++++ 1 file changed, 590 insertions(+) create mode 100644 scripts/ZoneMinder/lib/ZoneMinder/Control/TVIP672WI.pm diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/TVIP672WI.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/TVIP672WI.pm new file mode 100644 index 000000000..4828b65b0 --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/TVIP672WI.pm @@ -0,0 +1,590 @@ +# ========================================================================= +# +# ZoneMinder Trendnet TV-IP672WI IP Control Protocol Module, $Date: $, $Revision: $ +# Copyright (C) 2014 Vincent Giovannone +# Updated 2017 Michael Barkdoll +# +# +# ========================================================================== +# +# 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 Trendnet TV-IP672WI IP camera control +# protocol. +# +# Tested on Zoneminder 1.30.4 +# +# Under control capability: +# +# * Main: name it (suggest TVIP672WI), type is FFMPEG (or remote if you're using MJPEG), protocol is TVIP672WI +# * Main (more): Can wake, can sleep, can reset +# * Move: Can move, can move diagonally, can move mapped, can move relative +# * Pan: Can pan +# * Tilt: Can tilt +# +# * Presets: Has presets, num presets 20, has home preset (don't set presets via camera's web server, only set via ZM.) +# (I didn't test/use presets -MB) +# +# Under control tab in the monitor itself: +# +# * Controllable +# * Control type is the name you gave it in control capability above +# * Control device is the username and password you use to authenticate to the camera +# E.g., admin:password +# +# * Control address is the camera's ip address AND web port +# E.g., 192.168.1.100:80 +# Optionally, it can parse all username and password with the following format: +# http://admin:password@192.168.1.100:80 +# +# +# If using with anything but a TV-IP672WI (ex: TV-IP672PI), YOU MUST MATCH THE REALM TO MATCH YOUR CAMERA FURTHER DOWN! +# +# +# Due to how the TVIP672 represents presets internally, you MUST define the presets in order... i.e. 1,2,3,4... not 1,10,3,4. +# (see much further down for why, if you care...) +# +# Install this file with the following commands: +# +# sudo cp TVIP672WI.pm /usr/share/perl5/ZoneMinder/Control/ +# sudo chmod 755 /usr/share/perl5/ZoneMinder/Control/TVIP672WI.pm + + +package ZoneMinder::Control::TVIP672WI; + +use 5.006; +use strict; +use warnings; + +require ZoneMinder::Base; +require ZoneMinder::Control; + +our @ISA = qw(ZoneMinder::Control); + +# +# ******** YOU MUST CHANGE THE FOLLOWING LINES TO MATCH YOUR CAMERA! ********** +# +# I assume that "TV-IP672WI" would work for the TV-IP672PI, but can't test since I don't own one. +# +# TV-IP672WI works for the WI version, of course. +# +# Finally, the username is the username you'd like to authenticate as. +# +#our $REALM = 'TV-IP862IC'; +#our $REALM = 'TV-IP672PI'; + +our $REALM = 'TV-IP672WI'; +#our $REALM = 'TVIP672WI'; + +# $USERNAME and $PASSWORD are parsed from ControlDevice field in GUI +# E.g., username:password +# Note: values defined below are overriden by GUI values +our $USERNAME = ''; +our $PASSWORD = ''; + +# $ADDRESS is parsed from field, 'Control Address' +our $ADDRESS = ''; + +# ========================================================================== +# +# Trendnet TV-IP672PI Control Protocol +# +# ========================================================================== + +use ZoneMinder::Logger qw(:all); +use ZoneMinder::Config qw(:all); + +sub new +{ + my $class = shift; + my $id = shift; + my $self = ZoneMinder::Control->new( $id ); + bless( $self, $class ); + srand( time() ); + return $self; +} + +our $AUTOLOAD; + +sub AUTOLOAD +{ + my $self = shift; + my $class = ref($self) || croak( "$self not object" ); + my $name = $AUTOLOAD; + $name =~ s/.*://; + if ( exists($self->{$name}) ) + { + return( $self->{$name} ); + } + Fatal( "Can't access $name member of object of class $class" ); +} + +sub open +{ + my $self = shift; + $self->loadMonitor(); + + my $controldevice = $self->{Monitor}->{ControlDevice}; + if ( $controldevice && $controldevice =~ m/\:/ ) { + my ( $adminname, $lastpass ) + = split(/\:/, $controldevice, 2); + if ( $adminname ) { + $USERNAME = $adminname; + #Error( "Username updated to: " . $USERNAME ); + } + if ( $lastpass ) { + $PASSWORD = $lastpass; + #Error( "Password updated to: " . $PASSWORD ); + } + } else { + if ( $controldevice ) { + $PASSWORD = $controldevice; + #Error( "Password updated to: " . $PASSWORD ); + } else { + Error ( "Unable to parse Control Device field: " . $controldevice ); + } + } + + my ( $protocol, $username, $password, $address ) + = $self->{Monitor}->{ControlAddress} =~ /^(http?:\/\/)?([^:]+):([^\/@]+)@(.*)$/; + if ( ( $username ) && ( $password ) && ( $address ) ) { + $USERNAME = $username; + $PASSWORD = $password; + $ADDRESS = $address; + #Error( "Set USERNAME: " . $USERNAME . "PASSWORD: " . $PASSWORD . "ADDRESS: " . $ADDRESS ); + } else { + if ( ! ( $controldevice ) ) { + Error( "Failed to parse auth from address"); + Error( "Unable to pull username and password for authentication from fields ControlAddress or ControlDevice" ); + Error( "Control script attempts to pull these values from field ControlDevice as username:password" ); + Error( "Optionally, these values can also be set at /usr/share/perl5/ZoneMinder/Control/TVIP672WI.pm" ); + } + $ADDRESS = $self->{Monitor}->{ControlAddress}; + } + if ( ! ( $ADDRESS =~ m/\:/ ) ) { + $ADDRESS .= ':80'; + Debug( "You generally need to also specify the port. I will append :80" ); + } + + Debug ( "Address is now: " . $ADDRESS ); + + use LWP::UserAgent; + use HTTP::Request::Common; + $self->{ua} = LWP::UserAgent->new; + $self->{ua}->agent( "ZoneMinder Control Agent/".$ZoneMinder::Base::ZM_VERSION ); + $self->{state} = 'open'; + # credentials: ("ip:port" (no prefix!), realm (string), username (string), password (string) + $self->{ua}->credentials($ADDRESS,$REALM,$USERNAME,$PASSWORD); + +# Detect REALM + my $req = HTTP::Request->new( GET=>"http://".$ADDRESS."/cgi/ptdc.cgi" ); + my $res = $self->{ua}->request($req); + + if ( ! $res->is_success ) { + Debug("Need newer REALM"); + + if ( $res->status_line() eq '401 Unauthorized' ) { + my $headers = $res->headers(); + foreach my $k ( keys %$headers ) { + Error("Initial Header $k => $$headers{$k}"); + } # end foreach + if ( $$headers{'www-authenticate'} ) { + my ( $auth, $tokens ) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/; + if ( $tokens =~ /\w+="([^"]+)"/i ) { + $REALM = $1; + Debug( "Changing REALM to $REALM" ); + $self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD); + my $req = HTTP::Request->new( GET=>"http://".$ADDRESS."/cgi/ptdc.cgi" ); + my $res = $self->{ua}->request($req); + if ( ! $res->is_success ) { + Error ( "Unable to authenticate!!!" ); + } + } # end if + } else { + Error("No headers line"); + } # end if headers + } # end if $res->status_line() eq '401 Unauthorized' + } # end if ! $res->is_success +} + +sub close +{ + my $self = shift; + $self->{state} = 'closed'; +} + +sub printMsg +{ + my $self = shift; + my $msg = shift; + my $msg_len = length($msg); + + Debug( $msg."[".$msg_len."]" ); +} + +sub sendCmd +{ + +# This routine is used for all moving, which are all GET commands... + + my $self = shift; + my $cmd = shift; + + my $result = undef; + + # Assuming you've placed your camera on a secure vlan network for ip cameras... + my $url = "http://".$USERNAME.":".$PASSWORD."@".$ADDRESS."/cgi/ptdc.cgi?command=".$cmd; + # The following didn't work with the TV-IP672WI; required authentication despite previous auth + #my $url = "http://".$ADDRESS."/cgi/ptdc.cgi?command=".$cmd; + my $req = HTTP::Request->new( GET=>$url ); + + Debug ("sendCmd command: " . $url ); + + my $res = $self->{ua}->request($req); + + if ( $res->is_success ) { + $result = !undef; + } 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 { + Debug("Content was " . $res->content() ); + } + } + if ( ! $result ) { + Debug( "Error check failed: '".$res->status_line()."' cmd:'".$cmd."'" ); + } + } + + return( $result ); +} + + + +sub sendCmdPost +{ + +# +# This routine is used for setting/clearing presets and IR commands, which are POST commands... +# + + my $self = shift; + my $url = shift; + my $cmd = shift; + + my $result = undef; + + if ($url eq undef) + { + Error ("url passed to sendCmdPost is undefined."); + return(-1); + } + + Debug ("sendCmdPost url: " . $url . " cmd: " . $cmd); + + my $req = HTTP::Request->new(POST => "http://".$ADDRESS.$url); + $req->content_type('application/x-www-form-urlencoded'); + $req->content($cmd); + + Debug ( "sendCmdPost credentials control address:'".$ADDRESS."' realm:'" . $REALM . "' username:'" . $USERNAME . "' password:'".$PASSWORD."'"); + + my $res = $self->{ua}->request($req); + + if ( $res->is_success ) + { + $result = !undef; + } + else + { + Error( "sendCmdPost Error check failed: '".$res->status_line()."' cmd:'".$cmd."'" ); + if ( $res->status_line() eq '401 Unauthorized' ) { + Error( "sendCmdPost Error check failed: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD ); + } else { + Error( "sendCmdPost Error check failed: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD ); + } # endif + } + + return( $result ); +} + + + +sub move +{ + my $self = shift; + my $panSteps = shift; + my $tiltSteps = shift; + + my $cmd = "set_relative_pos&posX=$panSteps&posY=$tiltSteps"; + $self->sendCmd( $cmd ); +} + +sub moveRelUpLeft +{ + my $self = shift; + Debug( "Move Up Left" ); + $self->move(-3, 3); +} + +sub moveRelUp +{ + my $self = shift; + Debug( "Move Up" ); + $self->move(0, 3); +} + +sub moveRelUpRight +{ + my $self = shift; + Debug( "Move Up Right" ); + $self->move(3, 3); +} + +sub moveRelLeft +{ + my $self = shift; + Debug( "Move Left" ); + $self->move(-3, 0); +} + +sub moveRelRight +{ + my $self = shift; + Debug( "Move Right" ); + $self->move(3, 0); +} + +sub moveRelDownLeft +{ + my $self = shift; + Debug( "Move Down Left" ); + $self->move(-3, -3); +} + +sub moveRelDown +{ + my $self = shift; + Debug( "Move Down" ); + $self->move(0, -3); +} + +sub moveRelDownRight +{ + my $self = shift; + Debug( "Move Down Right" ); + $self->move(3, -3); +} + + +# moves the camera to center on the point that the user clicked on in the video image. +# This isn't mega accurate but good enough for most purposes + +sub moveMap +{ + + # If the camera moves too much, increase hscale and vscale. (...if it doesn't move enough, try decreasing!) + # They scale the movement and are here to compensate for manufacturing variation. + # It's never going to be perfect, so just get somewhere in the ballpark and call it a day. + # (Don't forget to kill the zmcontrol process while tweaking!) + + # 1280x800 + my $hscale = 31; + my $vscale = 25; + + # 1280x800 with fisheye + #my $hscale = 15; + #my $vscale = 15; + + # 640x400 + #my $hscale = 14; + #my $vscale = 12; + + + my $self = shift; + my $params = shift; + my $xcoord = $self->getParam( $params, 'xcoord' ); + my $ycoord = $self->getParam( $params, 'ycoord' ); + + my $hor = ($xcoord - ($self->{Monitor}->{Width} / 2))/$hscale; + my $ver = ($ycoord - ($self->{Monitor}->{Height} / 2))/$vscale; + + $hor = int($hor); + $ver = -1 * int($ver); + + Debug( "Move Map to $xcoord,$ycoord, hor=$hor, ver=$ver" ); + $self->move( $hor, $ver ); +} + + +# **** PRESETS **** +# +# OK, presets work a little funky but they DO work, provided you define them in order and don't skip any. +# +# The problem is that when you load the web page for this camera, it gives a list of preset names tied to index numbers. +# So let's say you have four presets... A, B, C, and D, and defined them in that order. +# So A is index 0, B is index 1, C is index 2, D is index 3. When you tell the camera to go to a preset, you actually tell it by number, not by name. +# (So "Go to D" is really "go to index 3".) +# +# Now let's say somebody deletes C via the camera's web GUI. The camera re-numbers the existing presets A=0, B=1, D=2. +# There's really no easy way for ZM to discover this re-numbering, so zoneminder would still send "go to preset 3" thinking +# it's telling the camera to go to point D. In actuality it's telling the camera to go to a preset that no longer exists. +# +# As long as you define your presets in order (i.e. define preset 1, then preset 2, then preset 3, etc.) everything will work just +# fine in ZoneMinder. +# +# (Home preset needs to be set via the camera's web gui, and is unaffected by any of this.) +# +# So that's the limitation: DEFINE YOUR PRESETS IN ORDER THROUGH (and only through!) ZM AND DON'T SKIP ANY. +# + + +sub presetClear +{ + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + my $cmd = "presetName=$preset&command=del"; + my $url = "/eng/admin/cam_control.cgi"; + Debug ("presetClear: " . $preset . " cmd: " . $cmd); + $self->sendCmdPost($url,$cmd); +} + + +sub presetSet +{ + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + my $cmd = "presetName=$preset&command=add"; + my $url = "/eng/admin/cam_control.cgi"; + Debug ("presetSet " . $preset . " cmd: " . $cmd); + $self->sendCmdPost ($url,$cmd); +} + +sub presetGoto +{ + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + $preset = $preset - 1; + Debug( "Goto Preset $preset" ); + my $cmd = "goto_preset_position&index=$preset"; + $self->sendCmd( $cmd ); +} + +sub presetHome +{ + my $self = shift; + Debug( "Home Preset" ); + my $cmd = "go_home"; + $self->sendCmd( $cmd ); +} + + +# +# **** IR CONTROLS **** +# +# +# Wake: Force IR on, always. (always night mode) +# +# Sleep: Force IR off, always. (always day mode) +# +# Reset: Automatic IR mode. (day/night mode determined by camera) +# + + +sub wake +{ + # force IR on ("always night mode") + + my $self = shift; + my $url = "/eng/admin/adv_audiovideo.cgi"; + my $cmd = "irMode=3"; + + Debug("Wake -- IR on"); + + $self->sendCmdPost ($url,$cmd); +} + +sub sleep +{ + # force IR off ("always day mode") + + my $self=shift; + my $url = "/eng/admin/adv_audiovideo.cgi"; + my $cmd = "irMode=2"; + + Debug("Sleep -- IR off"); + + $self->sendCmdPost ($url,$cmd); +} + +sub reset +{ + # IR auto + + my $self=shift; + my $url = "/eng/admin/adv_audiovideo.cgi"; + my $cmd = "irMode=0"; + + Debug("Reset -- IR auto"); + + $self->sendCmdPost ($url,$cmd); +} + + +1; +__END__ +# Below is stub documentation for your module. You'd better edit it! + +=head1 NAME + +ZoneMinder::Database - Perl extension for Trendnet TVIP672 + +=head1 SYNOPSIS + + use ZoneMinder::Database; + stuff this in /usr/share/perl5/ZoneMinder/Control , then eat a sandwich + +=head1 DESCRIPTION + +Stub documentation for Trendnet TVIP672, created by Vince, updated by Michael. + +=head2 EXPORT + +None by default. + + + +=head1 SEE ALSO + +Read the comments at the beginning of this file to see the usage for zoneminder 1.25.0 + + +=head1 AUTHOR + +Vincent Giovannone, I'd rather you not email me; sure no problem... + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2014 by Vincent Giovannone + +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 \ No newline at end of file From fa7f6088c886d91dbfd7865c28ac6acd5d5680e0 Mon Sep 17 00:00:00 2001 From: Michael Barkdoll Date: Mon, 9 Oct 2017 11:14:13 -0500 Subject: [PATCH 2/4] Update zm_create.sql.in Add support for TVIP672WI --- db/zm_create.sql.in | 1 + 1 file changed, 1 insertion(+) diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index 62424c286..fab696ac7 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -603,6 +603,7 @@ INSERT INTO `Controls` VALUES (NULL,'Netcat ONVIF','Ffmpeg','Netcat',0,0,1,1,0,0 INSERT INTO `Controls` VALUES (NULL,'Keekoon','Remote','Keekoon', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 6, 0, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); INSERT INTO `Controls` VALUES (NULL,'HikVision','Local','',0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,20,1,1,1,1,0,0,0,1,1,0,0,0,0,1,1,100,0,0,1,0,0,0,0,1,1,100,1,0,0,0); INSERT INTO `Controls` VALUES (NULL,'Maginon Supra IPC','cURL','MaginonIPC',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,4,0,1,1,1,0,0,1,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); +INSERT INTO `Controls` VALUES (NULL,'TVIP672WI','Remote','TVIP672WI',1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); -- -- Add some monitor preset values From 3dc424282b60ae10ea9f0bde1d36579a1e27e977 Mon Sep 17 00:00:00 2001 From: Andrew Bauer Date: Sat, 11 Aug 2018 11:56:16 -0500 Subject: [PATCH 3/4] Support quoting config variables (#2175) * allow handling of quotes in config files * copy paste error * surround zm_arptool in quotes --- conf.d/01-system-paths.conf.in | 2 +- scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in | 2 +- src/zm_config.cpp | 7 ++++--- web/includes/config.php.in | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/conf.d/01-system-paths.conf.in b/conf.d/01-system-paths.conf.in index 9f45abdbd..e97433ba2 100644 --- a/conf.d/01-system-paths.conf.in +++ b/conf.d/01-system-paths.conf.in @@ -50,4 +50,4 @@ ZM_PATH_SWAP=@ZM_TMPDIR@ # Full path to optional arp binary # ZoneMinder will find the arp binary automatically on most systems -ZM_PATH_ARP=@ZM_PATH_ARP@ +ZM_PATH_ARP="@ZM_PATH_ARP@" diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in index 7a86b51a8..d48747703 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/Config.pm.in @@ -149,7 +149,7 @@ BEGIN { foreach my $str ( <$CONFIG> ) { next if ( $str =~ /^\s*$/ ); next if ( $str =~ /^\s*#/ ); - my ( $name, $value ) = $str =~ /^\s*([^=\s]+)\s*=\s*(.*?)\s*$/; + my ( $name, $value ) = $str =~ /^\s*([^=\s]+)\s*=\s*[\'"]*(.*?)[\'"]*\s*$/; if ( ! $name ) { print( STDERR "Warning, bad line in $config_file: $str\n" ); next; diff --git a/src/zm_config.cpp b/src/zm_config.cpp index 0865bc6e4..6d63be98b 100644 --- a/src/zm_config.cpp +++ b/src/zm_config.cpp @@ -123,9 +123,9 @@ void process_configfile( char* configFile) { if ( *line_ptr == '\0' || *line_ptr == '#' ) continue; - // Remove trailing white space + // Remove trailing white space and trailing quotes char *temp_ptr = line_ptr+strlen(line_ptr)-1; - while ( *temp_ptr == ' ' || *temp_ptr == '\t' ) { + while ( *temp_ptr == ' ' || *temp_ptr == '\t' || *temp_ptr == '\'' || *temp_ptr == '\"') { *temp_ptr-- = '\0'; temp_ptr--; } @@ -147,8 +147,9 @@ void process_configfile( char* configFile) { temp_ptr--; } while ( *temp_ptr == ' ' || *temp_ptr == '\t' ); - // Remove leading white space from the value part + // Remove leading white space and leading quotes from the value part white_len = strspn( val_ptr, " \t" ); + white_len += strspn( val_ptr, "\'\"" ); val_ptr += white_len; if ( strcasecmp( name_ptr, "ZM_DB_HOST" ) == 0 ) diff --git a/web/includes/config.php.in b/web/includes/config.php.in index c6aa93c63..a2a89b224 100644 --- a/web/includes/config.php.in +++ b/web/includes/config.php.in @@ -212,7 +212,7 @@ function process_configfile($configFile) { continue; elseif ( preg_match( '/^\s*#/', $str )) continue; - elseif ( preg_match( '/^\s*([^=\s]+)\s*=\s*(.*?)\s*$/', $str, $matches )) + elseif ( preg_match( '/^\s*([^=\s]+)\s*=\s*[\'"]*(.*?)[\'"]*\s*$/', $str, $matches )) $configvals[$matches[1]] = $matches[2]; } fclose( $cfg ); From e6d1a9447e360b83b06df81d072bf8549540814a Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 13 Aug 2018 11:12:15 -0400 Subject: [PATCH 4/4] rename TVIP862 to Trendnet and delete TVIP672WI --- .../lib/ZoneMinder/Control/TVIP672WI.pm | 590 ------------------ .../Control/{TVIP862.pm => Trendnet.pm} | 31 +- 2 files changed, 8 insertions(+), 613 deletions(-) delete mode 100644 scripts/ZoneMinder/lib/ZoneMinder/Control/TVIP672WI.pm rename scripts/ZoneMinder/lib/ZoneMinder/Control/{TVIP862.pm => Trendnet.pm} (93%) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/TVIP672WI.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/TVIP672WI.pm deleted file mode 100644 index 4828b65b0..000000000 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/TVIP672WI.pm +++ /dev/null @@ -1,590 +0,0 @@ -# ========================================================================= -# -# ZoneMinder Trendnet TV-IP672WI IP Control Protocol Module, $Date: $, $Revision: $ -# Copyright (C) 2014 Vincent Giovannone -# Updated 2017 Michael Barkdoll -# -# -# ========================================================================== -# -# 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 Trendnet TV-IP672WI IP camera control -# protocol. -# -# Tested on Zoneminder 1.30.4 -# -# Under control capability: -# -# * Main: name it (suggest TVIP672WI), type is FFMPEG (or remote if you're using MJPEG), protocol is TVIP672WI -# * Main (more): Can wake, can sleep, can reset -# * Move: Can move, can move diagonally, can move mapped, can move relative -# * Pan: Can pan -# * Tilt: Can tilt -# -# * Presets: Has presets, num presets 20, has home preset (don't set presets via camera's web server, only set via ZM.) -# (I didn't test/use presets -MB) -# -# Under control tab in the monitor itself: -# -# * Controllable -# * Control type is the name you gave it in control capability above -# * Control device is the username and password you use to authenticate to the camera -# E.g., admin:password -# -# * Control address is the camera's ip address AND web port -# E.g., 192.168.1.100:80 -# Optionally, it can parse all username and password with the following format: -# http://admin:password@192.168.1.100:80 -# -# -# If using with anything but a TV-IP672WI (ex: TV-IP672PI), YOU MUST MATCH THE REALM TO MATCH YOUR CAMERA FURTHER DOWN! -# -# -# Due to how the TVIP672 represents presets internally, you MUST define the presets in order... i.e. 1,2,3,4... not 1,10,3,4. -# (see much further down for why, if you care...) -# -# Install this file with the following commands: -# -# sudo cp TVIP672WI.pm /usr/share/perl5/ZoneMinder/Control/ -# sudo chmod 755 /usr/share/perl5/ZoneMinder/Control/TVIP672WI.pm - - -package ZoneMinder::Control::TVIP672WI; - -use 5.006; -use strict; -use warnings; - -require ZoneMinder::Base; -require ZoneMinder::Control; - -our @ISA = qw(ZoneMinder::Control); - -# -# ******** YOU MUST CHANGE THE FOLLOWING LINES TO MATCH YOUR CAMERA! ********** -# -# I assume that "TV-IP672WI" would work for the TV-IP672PI, but can't test since I don't own one. -# -# TV-IP672WI works for the WI version, of course. -# -# Finally, the username is the username you'd like to authenticate as. -# -#our $REALM = 'TV-IP862IC'; -#our $REALM = 'TV-IP672PI'; - -our $REALM = 'TV-IP672WI'; -#our $REALM = 'TVIP672WI'; - -# $USERNAME and $PASSWORD are parsed from ControlDevice field in GUI -# E.g., username:password -# Note: values defined below are overriden by GUI values -our $USERNAME = ''; -our $PASSWORD = ''; - -# $ADDRESS is parsed from field, 'Control Address' -our $ADDRESS = ''; - -# ========================================================================== -# -# Trendnet TV-IP672PI Control Protocol -# -# ========================================================================== - -use ZoneMinder::Logger qw(:all); -use ZoneMinder::Config qw(:all); - -sub new -{ - my $class = shift; - my $id = shift; - my $self = ZoneMinder::Control->new( $id ); - bless( $self, $class ); - srand( time() ); - return $self; -} - -our $AUTOLOAD; - -sub AUTOLOAD -{ - my $self = shift; - my $class = ref($self) || croak( "$self not object" ); - my $name = $AUTOLOAD; - $name =~ s/.*://; - if ( exists($self->{$name}) ) - { - return( $self->{$name} ); - } - Fatal( "Can't access $name member of object of class $class" ); -} - -sub open -{ - my $self = shift; - $self->loadMonitor(); - - my $controldevice = $self->{Monitor}->{ControlDevice}; - if ( $controldevice && $controldevice =~ m/\:/ ) { - my ( $adminname, $lastpass ) - = split(/\:/, $controldevice, 2); - if ( $adminname ) { - $USERNAME = $adminname; - #Error( "Username updated to: " . $USERNAME ); - } - if ( $lastpass ) { - $PASSWORD = $lastpass; - #Error( "Password updated to: " . $PASSWORD ); - } - } else { - if ( $controldevice ) { - $PASSWORD = $controldevice; - #Error( "Password updated to: " . $PASSWORD ); - } else { - Error ( "Unable to parse Control Device field: " . $controldevice ); - } - } - - my ( $protocol, $username, $password, $address ) - = $self->{Monitor}->{ControlAddress} =~ /^(http?:\/\/)?([^:]+):([^\/@]+)@(.*)$/; - if ( ( $username ) && ( $password ) && ( $address ) ) { - $USERNAME = $username; - $PASSWORD = $password; - $ADDRESS = $address; - #Error( "Set USERNAME: " . $USERNAME . "PASSWORD: " . $PASSWORD . "ADDRESS: " . $ADDRESS ); - } else { - if ( ! ( $controldevice ) ) { - Error( "Failed to parse auth from address"); - Error( "Unable to pull username and password for authentication from fields ControlAddress or ControlDevice" ); - Error( "Control script attempts to pull these values from field ControlDevice as username:password" ); - Error( "Optionally, these values can also be set at /usr/share/perl5/ZoneMinder/Control/TVIP672WI.pm" ); - } - $ADDRESS = $self->{Monitor}->{ControlAddress}; - } - if ( ! ( $ADDRESS =~ m/\:/ ) ) { - $ADDRESS .= ':80'; - Debug( "You generally need to also specify the port. I will append :80" ); - } - - Debug ( "Address is now: " . $ADDRESS ); - - use LWP::UserAgent; - use HTTP::Request::Common; - $self->{ua} = LWP::UserAgent->new; - $self->{ua}->agent( "ZoneMinder Control Agent/".$ZoneMinder::Base::ZM_VERSION ); - $self->{state} = 'open'; - # credentials: ("ip:port" (no prefix!), realm (string), username (string), password (string) - $self->{ua}->credentials($ADDRESS,$REALM,$USERNAME,$PASSWORD); - -# Detect REALM - my $req = HTTP::Request->new( GET=>"http://".$ADDRESS."/cgi/ptdc.cgi" ); - my $res = $self->{ua}->request($req); - - if ( ! $res->is_success ) { - Debug("Need newer REALM"); - - if ( $res->status_line() eq '401 Unauthorized' ) { - my $headers = $res->headers(); - foreach my $k ( keys %$headers ) { - Error("Initial Header $k => $$headers{$k}"); - } # end foreach - if ( $$headers{'www-authenticate'} ) { - my ( $auth, $tokens ) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/; - if ( $tokens =~ /\w+="([^"]+)"/i ) { - $REALM = $1; - Debug( "Changing REALM to $REALM" ); - $self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD); - my $req = HTTP::Request->new( GET=>"http://".$ADDRESS."/cgi/ptdc.cgi" ); - my $res = $self->{ua}->request($req); - if ( ! $res->is_success ) { - Error ( "Unable to authenticate!!!" ); - } - } # end if - } else { - Error("No headers line"); - } # end if headers - } # end if $res->status_line() eq '401 Unauthorized' - } # end if ! $res->is_success -} - -sub close -{ - my $self = shift; - $self->{state} = 'closed'; -} - -sub printMsg -{ - my $self = shift; - my $msg = shift; - my $msg_len = length($msg); - - Debug( $msg."[".$msg_len."]" ); -} - -sub sendCmd -{ - -# This routine is used for all moving, which are all GET commands... - - my $self = shift; - my $cmd = shift; - - my $result = undef; - - # Assuming you've placed your camera on a secure vlan network for ip cameras... - my $url = "http://".$USERNAME.":".$PASSWORD."@".$ADDRESS."/cgi/ptdc.cgi?command=".$cmd; - # The following didn't work with the TV-IP672WI; required authentication despite previous auth - #my $url = "http://".$ADDRESS."/cgi/ptdc.cgi?command=".$cmd; - my $req = HTTP::Request->new( GET=>$url ); - - Debug ("sendCmd command: " . $url ); - - my $res = $self->{ua}->request($req); - - if ( $res->is_success ) { - $result = !undef; - } 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 { - Debug("Content was " . $res->content() ); - } - } - if ( ! $result ) { - Debug( "Error check failed: '".$res->status_line()."' cmd:'".$cmd."'" ); - } - } - - return( $result ); -} - - - -sub sendCmdPost -{ - -# -# This routine is used for setting/clearing presets and IR commands, which are POST commands... -# - - my $self = shift; - my $url = shift; - my $cmd = shift; - - my $result = undef; - - if ($url eq undef) - { - Error ("url passed to sendCmdPost is undefined."); - return(-1); - } - - Debug ("sendCmdPost url: " . $url . " cmd: " . $cmd); - - my $req = HTTP::Request->new(POST => "http://".$ADDRESS.$url); - $req->content_type('application/x-www-form-urlencoded'); - $req->content($cmd); - - Debug ( "sendCmdPost credentials control address:'".$ADDRESS."' realm:'" . $REALM . "' username:'" . $USERNAME . "' password:'".$PASSWORD."'"); - - my $res = $self->{ua}->request($req); - - if ( $res->is_success ) - { - $result = !undef; - } - else - { - Error( "sendCmdPost Error check failed: '".$res->status_line()."' cmd:'".$cmd."'" ); - if ( $res->status_line() eq '401 Unauthorized' ) { - Error( "sendCmdPost Error check failed: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD ); - } else { - Error( "sendCmdPost Error check failed: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD ); - } # endif - } - - return( $result ); -} - - - -sub move -{ - my $self = shift; - my $panSteps = shift; - my $tiltSteps = shift; - - my $cmd = "set_relative_pos&posX=$panSteps&posY=$tiltSteps"; - $self->sendCmd( $cmd ); -} - -sub moveRelUpLeft -{ - my $self = shift; - Debug( "Move Up Left" ); - $self->move(-3, 3); -} - -sub moveRelUp -{ - my $self = shift; - Debug( "Move Up" ); - $self->move(0, 3); -} - -sub moveRelUpRight -{ - my $self = shift; - Debug( "Move Up Right" ); - $self->move(3, 3); -} - -sub moveRelLeft -{ - my $self = shift; - Debug( "Move Left" ); - $self->move(-3, 0); -} - -sub moveRelRight -{ - my $self = shift; - Debug( "Move Right" ); - $self->move(3, 0); -} - -sub moveRelDownLeft -{ - my $self = shift; - Debug( "Move Down Left" ); - $self->move(-3, -3); -} - -sub moveRelDown -{ - my $self = shift; - Debug( "Move Down" ); - $self->move(0, -3); -} - -sub moveRelDownRight -{ - my $self = shift; - Debug( "Move Down Right" ); - $self->move(3, -3); -} - - -# moves the camera to center on the point that the user clicked on in the video image. -# This isn't mega accurate but good enough for most purposes - -sub moveMap -{ - - # If the camera moves too much, increase hscale and vscale. (...if it doesn't move enough, try decreasing!) - # They scale the movement and are here to compensate for manufacturing variation. - # It's never going to be perfect, so just get somewhere in the ballpark and call it a day. - # (Don't forget to kill the zmcontrol process while tweaking!) - - # 1280x800 - my $hscale = 31; - my $vscale = 25; - - # 1280x800 with fisheye - #my $hscale = 15; - #my $vscale = 15; - - # 640x400 - #my $hscale = 14; - #my $vscale = 12; - - - my $self = shift; - my $params = shift; - my $xcoord = $self->getParam( $params, 'xcoord' ); - my $ycoord = $self->getParam( $params, 'ycoord' ); - - my $hor = ($xcoord - ($self->{Monitor}->{Width} / 2))/$hscale; - my $ver = ($ycoord - ($self->{Monitor}->{Height} / 2))/$vscale; - - $hor = int($hor); - $ver = -1 * int($ver); - - Debug( "Move Map to $xcoord,$ycoord, hor=$hor, ver=$ver" ); - $self->move( $hor, $ver ); -} - - -# **** PRESETS **** -# -# OK, presets work a little funky but they DO work, provided you define them in order and don't skip any. -# -# The problem is that when you load the web page for this camera, it gives a list of preset names tied to index numbers. -# So let's say you have four presets... A, B, C, and D, and defined them in that order. -# So A is index 0, B is index 1, C is index 2, D is index 3. When you tell the camera to go to a preset, you actually tell it by number, not by name. -# (So "Go to D" is really "go to index 3".) -# -# Now let's say somebody deletes C via the camera's web GUI. The camera re-numbers the existing presets A=0, B=1, D=2. -# There's really no easy way for ZM to discover this re-numbering, so zoneminder would still send "go to preset 3" thinking -# it's telling the camera to go to point D. In actuality it's telling the camera to go to a preset that no longer exists. -# -# As long as you define your presets in order (i.e. define preset 1, then preset 2, then preset 3, etc.) everything will work just -# fine in ZoneMinder. -# -# (Home preset needs to be set via the camera's web gui, and is unaffected by any of this.) -# -# So that's the limitation: DEFINE YOUR PRESETS IN ORDER THROUGH (and only through!) ZM AND DON'T SKIP ANY. -# - - -sub presetClear -{ - my $self = shift; - my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - my $cmd = "presetName=$preset&command=del"; - my $url = "/eng/admin/cam_control.cgi"; - Debug ("presetClear: " . $preset . " cmd: " . $cmd); - $self->sendCmdPost($url,$cmd); -} - - -sub presetSet -{ - my $self = shift; - my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - my $cmd = "presetName=$preset&command=add"; - my $url = "/eng/admin/cam_control.cgi"; - Debug ("presetSet " . $preset . " cmd: " . $cmd); - $self->sendCmdPost ($url,$cmd); -} - -sub presetGoto -{ - my $self = shift; - my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - $preset = $preset - 1; - Debug( "Goto Preset $preset" ); - my $cmd = "goto_preset_position&index=$preset"; - $self->sendCmd( $cmd ); -} - -sub presetHome -{ - my $self = shift; - Debug( "Home Preset" ); - my $cmd = "go_home"; - $self->sendCmd( $cmd ); -} - - -# -# **** IR CONTROLS **** -# -# -# Wake: Force IR on, always. (always night mode) -# -# Sleep: Force IR off, always. (always day mode) -# -# Reset: Automatic IR mode. (day/night mode determined by camera) -# - - -sub wake -{ - # force IR on ("always night mode") - - my $self = shift; - my $url = "/eng/admin/adv_audiovideo.cgi"; - my $cmd = "irMode=3"; - - Debug("Wake -- IR on"); - - $self->sendCmdPost ($url,$cmd); -} - -sub sleep -{ - # force IR off ("always day mode") - - my $self=shift; - my $url = "/eng/admin/adv_audiovideo.cgi"; - my $cmd = "irMode=2"; - - Debug("Sleep -- IR off"); - - $self->sendCmdPost ($url,$cmd); -} - -sub reset -{ - # IR auto - - my $self=shift; - my $url = "/eng/admin/adv_audiovideo.cgi"; - my $cmd = "irMode=0"; - - Debug("Reset -- IR auto"); - - $self->sendCmdPost ($url,$cmd); -} - - -1; -__END__ -# Below is stub documentation for your module. You'd better edit it! - -=head1 NAME - -ZoneMinder::Database - Perl extension for Trendnet TVIP672 - -=head1 SYNOPSIS - - use ZoneMinder::Database; - stuff this in /usr/share/perl5/ZoneMinder/Control , then eat a sandwich - -=head1 DESCRIPTION - -Stub documentation for Trendnet TVIP672, created by Vince, updated by Michael. - -=head2 EXPORT - -None by default. - - - -=head1 SEE ALSO - -Read the comments at the beginning of this file to see the usage for zoneminder 1.25.0 - - -=head1 AUTHOR - -Vincent Giovannone, I'd rather you not email me; sure no problem... - -=head1 COPYRIGHT AND LICENSE - -Copyright (C) 2014 by Vincent Giovannone - -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 \ No newline at end of file diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/TVIP862.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Trendnet.pm similarity index 93% rename from scripts/ZoneMinder/lib/ZoneMinder/Control/TVIP862.pm rename to scripts/ZoneMinder/lib/ZoneMinder/Control/Trendnet.pm index ec6e5a95e..a30aaaf46 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/TVIP862.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Trendnet.pm @@ -1,7 +1,7 @@ #=========================================================================== # -# ZoneMinder Trendnet TV-IP862IC IP Control Protocol Module, $Date: $, $Revision: $ -# Copyright (C) 2014 Vincent Giovannone +# ZoneMinder Trendnet IP Control Protocol Module, $Date: $, $Revision: $ +# Copyright (C) 2018 ZoneMinder LLC8 ZoneMinder LLC8 ZoneMinder LLC8 ZoneMinder LLC8 ZoneMinder LLC8 ZoneMinder LLC8 ZoneMinder LLC8 ZoneMinder LLC # # # ========================================================================== @@ -18,12 +18,11 @@ # ========================================================================== # # This module contains the implementation of the Trendnet # IP camera control -# protocol. Has been testing with TV-IP862IC and TV-IP672PI +# protocol. Has been tested with TV-IP862IC # # Under control capability: # -# * Main: name it (suggest TVIP672PI), type is FFMPEG (or remote if you're using MJPEG), protocol is TVIP672PI -# * Main (more): Can wake, can sleep, can reset +# * Main: Can wake, can sleep, can reset # * Move: Can move, can move diagonally, can move mapped, can move relative # * Pan: Can pan # * Tilt: Can tilt @@ -37,12 +36,8 @@ # You can also put the authentication information here and change the # protocol to https using something like https://admin:password@192.168.1.1:80 # -# -# Due to how the TVIP672 represents presets internally, you MUST define the presets in order... i.e. 1,2,3,4... not 1,10,3,4. -# (see much further down for why, if you care...) -# -package ZoneMinder::Control::TVIP862; +package ZoneMinder::Control::Trendnet; use 5.006; use strict; @@ -62,7 +57,7 @@ our @ISA = qw(ZoneMinder::Control); # The username and password should be passed in the ControlDevice field but you # can set them here if you want. # -our $REALM = 'TV-IP862IC'; +our $REALM = ''; our $PROTOCOL = 'http://'; our $USERNAME = 'admin'; our $PASSWORD = ''; @@ -431,7 +426,7 @@ __END__ =head1 NAME -ZoneMinder::Database - Perl extension for Trendnet TVIP672 +ZoneMinder::Control::Trendnet - Perl module for Trendnet cameras =head1 SYNOPSIS @@ -440,22 +435,12 @@ ZoneMinder::Database - Perl extension for Trendnet TVIP672 =head1 DESCRIPTION -Stub documentation for Trendnet TVIP672, created by Vince. +Stub documentation for Trendnet PTZ Sctrol =head2 EXPORT None by default. - - -=head1 SEE ALSO - -Read the comments at the beginning of this file to see the usage for zoneminder 1.25.0 - - -=head1 AUTHOR - - =head1 COPYRIGHT AND LICENSE Copyright (C) 2018 by ZoneMinder LLC