From 223b618f747d615a55bbfd3199a157a36223e492 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 18 Feb 2020 11:08:05 -0500 Subject: [PATCH 1/9] quotes --- .../ZoneMinder/lib/ZoneMinder/Control/HikVision.pm | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm index 8754500fa..a9f8d8f1b 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm @@ -95,9 +95,9 @@ sub PutCmd { my $self = shift; my $cmd = shift; my $content = shift; - my $req = HTTP::Request->new(PUT => "$self->{BaseURL}/$cmd"); + 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_type('application/x-www-form-urlencoded; charset=UTF-8'); $req->content('' . "\n" . $content); } my $res = $self->{UA}->request($req); @@ -135,13 +135,13 @@ sub PutCmd { # Check for username/password # if ( $self->{Monitor}{ControlAddress} =~ /.+:(.+)@.+/ ) { - Info("Check username/password is correct"); + Info('Check username/password is correct'); } elsif ( $self->{Monitor}{ControlAddress} =~ /^[^:]+@.+/ ) { - Info("No password in Control Address. Should there be one?"); + Info('No password in Control Address. Should there be one?'); } elsif ( $self->{Monitor}{ControlAddress} =~ /^:.+@.+/ ) { - Info("Password but no username in Control Address."); + Info('Password but no username in Control Address.'); } else { - Info("Missing username and password in Control Address."); + Info('Missing username and password in Control Address.'); } Fatal($res->status_line); } @@ -382,7 +382,7 @@ sub irisRelOpen { sub reset { my $self = shift; - $self->PutCmd("ISAPI/System/reboot"); + $self->PutCmd('ISAPI/System/reboot'); } 1; From 568c42cfa9c4858db8875b7e4ce6489ec010ac74 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 18 Feb 2020 13:05:45 -0500 Subject: [PATCH 2/9] Use URI to better handle ControlAddress parsing. --- .../lib/ZoneMinder/Control/Amcrest_HTTP.pm | 46 ++++++++++++------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm index dd30b2070..dfae1c53f 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm @@ -29,6 +29,7 @@ use Time::HiRes qw( usleep ); require ZoneMinder::Base; require ZoneMinder::Control; require LWP::UserAgent; +use URI; our @ISA = qw(ZoneMinder::Control); @@ -53,20 +54,33 @@ sub open { my $self = shift; $self->loadMonitor(); - my $username; - my $password; - my $realm = 'Login to ' . $self->{Monitor}->{ControlDevice}; + if ( $self->{Monitor}->{ControlAddress} !~ /^\w+:\/\// ) { + # Has no scheme at the beginning, so won't parse as a URI + $self->{Monitor}->{ControlAddress} = 'http://'.$self->{Monitor}->{ControlAddress}; + } + my $uri = URI->new($self->{Monitor}->{ControlAddress}); + + $uri->port(80) if ! $uri->port(); + my $ADDRESS = $uri->scheme.'://'.$uri->authority().$uri->path().($uri->port()?':'.$uri->port():''); + Debug("Address: $ADDRESS"); $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent('ZoneMinder Control Agent/'.ZoneMinder::Base::ZM_VERSION); - if ( $self->{Monitor}->{ControlAddress} =~ /(.*):(.*)@(.*)/ ) { - $username = $1; - $password = $2; - $$self{address} = $3; + my ( $username, $password ); + my $realm = 'Login to ' . $self->{Monitor}->{ControlDevice}; + if ( $self->{Monitor}->{ControlAddress} ) { + ( $username, $password ) = $uri->authority() =~ /^(.*):(.*)@(.*)$/; + + $$self{address} = $uri->authority().($uri->port() ? ':'.$uri->port() : ''); + if ( $$self{address} !~ /:/ ) { + Debug('Adding default port to Control Address'); + $$self{address} .= ':80'; + $self->{Monitor}->{ControlDevice} .= ':80'; + } $self->{ua}->credentials($$self{address}, $realm, $username, $password); # Testing seems to show that we need the username/password in each url as well as credentials - $$self{base_url} = 'http://'.$self->{Monitor}->{ControlDevice}; - Debug("Using initial credentials for $$self{address}, $realm, $username, $password"); + $$self{base_url} = $self->{Monitor}->{ControlAddress}; + Debug("Using initial credentials for $$self{address}, $realm, $username, $password, base_url: $$self{base_url} auth:".$uri->authority()); } # Detect REALM, has to be /cgi-bin/ptz.cgi because just / accepts no auth @@ -91,7 +105,7 @@ sub open { $realm = $1; Debug("Changing REALM to ($realm)"); $self->{ua}->credentials($$self{address}, $realm, $username, $password); - $res = $self->{ua}->get($$self{address}.'/cgi-bin/ptz.cgi'); + $res = $self->{ua}->get($$self{base_url}.'/cgi-bin/ptz.cgi'); if ( $res->is_success() ) { $self->{state} = 'open'; return; @@ -116,7 +130,7 @@ sub open { Debug('No headers line'); } # end if headers } else { - Error("Failed to get $$self{address}/cgi-bin/ptz.cgi ".$res->status_line()); + Error("Failed to get $$self{base_url}/cgi-bin/ptz.cgi ".$res->status_line()); } # end if $res->status_line() eq '401 Unauthorized' @@ -135,21 +149,21 @@ sub sendCmd { $self->printMsg($cmd, 'Tx'); - my $res = $self->{ua}->get("http://$$self{address}/$cmd"); + my $res = $self->{ua}->get($$self{base_url}.'/'.$cmd); if ( $res->is_success ) { $result = !undef; # Command to camera appears successful, write Info item to log - Info('Camera control: \''.$res->status_line().'\' for URL '.$$self{address}.'/'.$cmd); + Info('Camera control: \''.$res->status_line().'\' for URL '.$$self{base_url}.'/'.$cmd); # TODO: Add code to retrieve $res->message_decode or some such. Then we could do things like check the camera status. } else { # Try again - $res = $self->{ua}->get("http://$$self{address}/$cmd"); + $res = $self->{ua}->get($$self{base_url}.'/'.$cmd); if ( $res->is_success ) { # Command to camera appears successful, write Info item to log - Info('Camera control: \''.$res->status_line().'\' for URL '.$$self{address}.'/'.$cmd); + Info('Camera control: \''.$res->status_line().'\' for URL '.$$self{base_url}.'/'.$cmd); } else { - Error('Camera control command FAILED: \''.$res->status_line().'\' for URL '.$$self{address}.'/'.$cmd); + Error('Camera control command FAILED: \''.$res->status_line().'\' for URL '.$$self{base_url}.'/'.$cmd); $res = $self->{ua}->get('http://'.$self->{Monitor}->{ControlAddress}.'/'.$cmd); } } From 789db4e5a6fd982e0520e4397e4af28bf7b1d66e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 18 Feb 2020 13:29:06 -0500 Subject: [PATCH 3/9] Fix filter new rows missing their onchange event on attribute dropdown. Fixes #2817 --- web/skins/classic/views/js/filter.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/web/skins/classic/views/js/filter.js b/web/skins/classic/views/js/filter.js index a4d3a4bcd..5144c4734 100644 --- a/web/skins/classic/views/js/filter.js +++ b/web/skins/classic/views/js/filter.js @@ -158,18 +158,18 @@ function parseRows(rows) { } var brackets = rows.length - 2; - if (brackets > 0) { //add bracket select to all rows + if ( brackets > 0 ) { // add bracket select to all rows var obrSelect = $j('').attr('name', queryPrefix + rowNum + '][obr]').attr('id', queryPrefix + rowNum + '][obr]'); var cbrSelect = $j('').attr('name', queryPrefix + rowNum + '][cbr]').attr('id', queryPrefix + rowNum + '][cbr]'); obrSelect.append(''); cbrSelect.append(''); } - var obrVal = inputTds.eq(1).children().val() != undefined ? inputTds.eq(1).children().val() : 0; //Save currently selected bracket option + var obrVal = inputTds.eq(1).children().val() != undefined ? inputTds.eq(1).children().val() : 0; // Save currently selected bracket option var cbrVal = inputTds.eq(5).children().val() != undefined ? inputTds.eq(5).children().val() : 0; - inputTds.eq(1).html(obrSelect).children().val(obrVal); //Set bracket contents and assign saved value + inputTds.eq(1).html(obrSelect).children().val(obrVal); // Set bracket contents and assign saved value inputTds.eq(5).html(cbrSelect).children().val(cbrVal); } else { inputTds.eq(1).html(' '); // Blank if there aren't enough terms for brackets @@ -177,14 +177,13 @@ function parseRows(rows) { } if ( rows.length == 1 ) { - inputTds.eq(6).find('button[data-on-click-this="delTerm"]').prop('disabled', true); //enable/disable remove row button + inputTds.eq(6).find('button[data-on-click-this="delTerm"]').prop('disabled', true); // enable/disable remove row button } else { inputTds.eq(6).find('button[data-on-click-this="delTerm"]').prop('disabled', false); } var attr = inputTds.eq(2).children().val(); - - if ( attr == "Archived" ) { //Archived types + if ( attr == 'Archived' ) { // Archived types inputTds.eq(3).html('equal to'); var archiveSelect = $j('').attr('name', queryPrefix + rowNum + '][val]').attr('id', queryPrefix + rowNum + '][val]'); for (var i = 0; i < archiveTypes.length; i++) { @@ -274,11 +273,16 @@ function addTerm( element ) { this[0].selected = 'selected'; }).chosen({width: '101%'}); newRow.find('input[type="text"]').val(''); - newRow[0].querySelectorAll("button[data-on-click-this]").forEach(function attachOnClick(el) { + newRow[0].querySelectorAll("button[data-on-click-this]").forEach(function(el) { var fnName = el.getAttribute("data-on-click-this"); el.onclick = window[fnName].bind(el, el); }); + newRow[0].querySelectorAll('select[data-on-change-this]').forEach(function(el) { + var fnName = el.getAttribute('data-on-change-this'); + el.onchange = window[fnName].bind(el, el); + }); + var rows = $j(row).parent().children(); parseRows(rows); } From 9a21ebf696c7319733294e2376d5a2aa7c31342b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 18 Feb 2020 15:10:44 -0500 Subject: [PATCH 4/9] Remove unneeded test for :80 --- scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm | 5 ----- 1 file changed, 5 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm index dfae1c53f..6416e3ee3 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm @@ -72,11 +72,6 @@ sub open { ( $username, $password ) = $uri->authority() =~ /^(.*):(.*)@(.*)$/; $$self{address} = $uri->authority().($uri->port() ? ':'.$uri->port() : ''); - if ( $$self{address} !~ /:/ ) { - Debug('Adding default port to Control Address'); - $$self{address} .= ':80'; - $self->{Monitor}->{ControlDevice} .= ':80'; - } $self->{ua}->credentials($$self{address}, $realm, $username, $password); # Testing seems to show that we need the username/password in each url as well as credentials $$self{base_url} = $self->{Monitor}->{ControlAddress}; From cac7ddd89d5d438d30771094469e0ee06576dab5 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 18 Feb 2020 16:48:08 -0500 Subject: [PATCH 5/9] Discover that authority also contains port. Remove redundant stuff. --- .../lib/ZoneMinder/Control/Amcrest_HTTP.pm | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm index 6416e3ee3..59e4f7511 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm @@ -60,10 +60,6 @@ sub open { } my $uri = URI->new($self->{Monitor}->{ControlAddress}); - $uri->port(80) if ! $uri->port(); - my $ADDRESS = $uri->scheme.'://'.$uri->authority().$uri->path().($uri->port()?':'.$uri->port():''); - Debug("Address: $ADDRESS"); - $self->{ua} = LWP::UserAgent->new; $self->{ua}->agent('ZoneMinder Control Agent/'.ZoneMinder::Base::ZM_VERSION); my ( $username, $password ); @@ -71,15 +67,15 @@ sub open { if ( $self->{Monitor}->{ControlAddress} ) { ( $username, $password ) = $uri->authority() =~ /^(.*):(.*)@(.*)$/; - $$self{address} = $uri->authority().($uri->port() ? ':'.$uri->port() : ''); - $self->{ua}->credentials($$self{address}, $realm, $username, $password); + $$self{address} = $uri->host_port(); + $self->{ua}->credentials($uri->host_port(), $realm, $username, $password); # Testing seems to show that we need the username/password in each url as well as credentials - $$self{base_url} = $self->{Monitor}->{ControlAddress}; - Debug("Using initial credentials for $$self{address}, $realm, $username, $password, base_url: $$self{base_url} auth:".$uri->authority()); + $$self{base_url} = $uri->canonical(); + Debug('Using initial credentials for '.$uri->host_port().", $realm, $username, $password, base_url: $$self{base_url} auth:".$uri->authority()); } # Detect REALM, has to be /cgi-bin/ptz.cgi because just / accepts no auth - my $res = $self->{ua}->get($$self{base_url}.'/cgi-bin/ptz.cgi'); + my $res = $self->{ua}->get($$self{base_url}.'cgi-bin/ptz.cgi'); if ( $res->is_success ) { $self->{state} = 'open'; @@ -100,7 +96,7 @@ sub open { $realm = $1; Debug("Changing REALM to ($realm)"); $self->{ua}->credentials($$self{address}, $realm, $username, $password); - $res = $self->{ua}->get($$self{base_url}.'/cgi-bin/ptz.cgi'); + $res = $self->{ua}->get($$self{base_url}.'cgi-bin/ptz.cgi'); if ( $res->is_success() ) { $self->{state} = 'open'; return; @@ -125,7 +121,7 @@ sub open { Debug('No headers line'); } # end if headers } else { - Error("Failed to get $$self{base_url}/cgi-bin/ptz.cgi ".$res->status_line()); + Error("Failed to get $$self{base_url}cgi-bin/ptz.cgi ".$res->status_line()); } # end if $res->status_line() eq '401 Unauthorized' @@ -144,21 +140,21 @@ sub sendCmd { $self->printMsg($cmd, 'Tx'); - my $res = $self->{ua}->get($$self{base_url}.'/'.$cmd); + my $res = $self->{ua}->get($$self{base_url}.$cmd); if ( $res->is_success ) { $result = !undef; # Command to camera appears successful, write Info item to log - Info('Camera control: \''.$res->status_line().'\' for URL '.$$self{base_url}.'/'.$cmd); + Info('Camera control: \''.$res->status_line().'\' for URL '.$$self{base_url}.$cmd); # TODO: Add code to retrieve $res->message_decode or some such. Then we could do things like check the camera status. } else { # Try again - $res = $self->{ua}->get($$self{base_url}.'/'.$cmd); + $res = $self->{ua}->get($$self{base_url}.$cmd); if ( $res->is_success ) { # Command to camera appears successful, write Info item to log - Info('Camera control: \''.$res->status_line().'\' for URL '.$$self{base_url}.'/'.$cmd); + Info('Camera control 2: \''.$res->status_line().'\' for URL '.$$self{base_url}.$cmd); } else { - Error('Camera control command FAILED: \''.$res->status_line().'\' for URL '.$$self{base_url}.'/'.$cmd); + Error('Camera control command FAILED: \''.$res->status_line().'\' for URL '.$$self{base_url}.$cmd); $res = $self->{ua}->get('http://'.$self->{Monitor}->{ControlAddress}.'/'.$cmd); } } From 1168db9df01dae24655266b38a43a33c4216c319 Mon Sep 17 00:00:00 2001 From: zlodag <11688686+zlodag@users.noreply.github.com> Date: Wed, 19 Feb 2020 22:47:40 +1300 Subject: [PATCH 6/9] Fixed regexp in zmcamtool to accept names for currently existing MonitorPresets --- scripts/zmcamtool.pl.in | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/zmcamtool.pl.in b/scripts/zmcamtool.pl.in index b7665d7d2..7e345b79d 100644 --- a/scripts/zmcamtool.pl.in +++ b/scripts/zmcamtool.pl.in @@ -352,8 +352,7 @@ sub exportsql { } my $name = $ARGV[0]; - if ($name) { - $name =~ /([A-Za-z0-9 -]*)/; # Only allow alphanumeric, dash and space + if ($name && $name =~ /^([A-Za-z0-9 ,.&()\/\-]+)$/) { # Allow alphanumeric and " ,.&()/-" $name = $1; $command .= qq( --where="Name = '$name'"); } From 0c97bff64527de7e832c64623baec00be49ea2c1 Mon Sep 17 00:00:00 2001 From: zlodag <11688686+zlodag@users.noreply.github.com> Date: Wed, 19 Feb 2020 22:50:09 +1300 Subject: [PATCH 7/9] monitor presets and controls for D-Link DCS-5020L --- db/zm_create.sql.in | 2 + .../lib/ZoneMinder/Control/DCS5020L.pm | 356 ++++++++++++++++++ 2 files changed, 358 insertions(+) create mode 100644 scripts/ZoneMinder/lib/ZoneMinder/Control/DCS5020L.pm diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index c9b2b6ea1..6b561a116 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -808,6 +808,7 @@ INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-423','Ffmpeg','Reolink',0,0,1,0 INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-411','Ffmpeg','Reolink',0,0,1,0,1,0,0,0,1,0,0,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-420','Ffmpeg','Reolink',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,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); INSERT INTO `Controls` VALUES (NULL,'D-LINK DCS-3415','Remote','DCS3415',0,0,0,0,1,0,0,0,0,0,0,0,0,0,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,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,'D-Link DCS-5020L','Remote','DCS5020L',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,1,24,1,0,1,1,1,0,1,0,1,0,0,1,30,0,0,0,0,0,1,0,0,1,30,0,0,0,0,0,0,0); INSERT INTO `Controls` VALUES (NULL,'IOS Camera','Ffmpeg','IPCAMIOS',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,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,'Dericam P2','Ffmpeg','DericamP2',0,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,1,10,0,1,1,1,0,0,0,1,1,0,0,0,0,1,1,45,0,0,1,0,0,0,0,1,1,45,0,0,0,0); INSERT INTO `Controls` VALUES (NULL,'Trendnet','Remote','Trendnet',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,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); @@ -842,6 +843,7 @@ INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, multicast','Remote','rt INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, RTP/RTSP','Remote','rtsp',0,255,'rtsp','rtpRtsp','',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, RTP/RTSP/HTTP','Remote',NULL,NULL,NULL,'rtsp','rtpRtspHttp','',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); INSERT INTO MonitorPresets VALUES (NULL,'D-link DCS-930L, 640x480, mjpeg','Remote','http',0,0,'http','simple','',80,'/mjpeg.cgi',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,'D-Link DCS-5020L, 640x480, mjpeg','Remote','http',0,0,'http','simple',':@','80','/video.cgi',NULL,640,480,0,NULL,1,'34',NULL,':@',100,100); INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, mpjpeg','Remote','http',0,0,'http','simple','',80,'/nphMotionJpeg?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, jpeg','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,5.0,0,NULL,NULL,NULL,100,100); diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/DCS5020L.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/DCS5020L.pm new file mode 100644 index 000000000..59d9e3550 --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/DCS5020L.pm @@ -0,0 +1,356 @@ +# =========================================================================r +# +# ZoneMinder D-Link DCS-5020L IP Control Protocol Module, $Date: $, $Revision: $ +# Copyright (C) 2013 Art Scheel +# +# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# ========================================================================== +# +# This module contains the implementation of the D-Link DCS-5020L IP camera control +# protocol. +# +package ZoneMinder::Control::DCS5020L; + +use 5.006; +use strict; +use warnings; + +require ZoneMinder::Base; +require ZoneMinder::Control; + +our @ISA = qw(ZoneMinder::Control); + +our $VERSION = $ZoneMinder::Base::VERSION; + +# ========================================================================== +# +# D-Link DCS-5020L Control Protocol +# +# ========================================================================== + +use ZoneMinder::Logger qw(:all); +use ZoneMinder::Config qw(:all); + +use Time::HiRes qw( usleep ); + +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(); + + use LWP::UserAgent; + $self->{ua} = LWP::UserAgent->new; + $self->{ua}->agent( "ZoneMinder Control Agent/" . ZoneMinder::Base::ZM_VERSION ); + $self->{state} = 'open'; +} + +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 +{ + my $self = shift; + my $cmd = shift; + my $cgi = shift; + + my $result = undef; + + printMsg( $cmd, "Tx" ); + + my $req = HTTP::Request->new( POST=>"http://$self->{Monitor}->{ControlAddress}/$cgi.cgi" ); + $req->content($cmd); + my $res = $self->{ua}->request($req); + + if ( $res->is_success ) + { + $result = !undef; + } + else + { + Error( "Error check failed: '".$res->status_line()."'" ); + } + + return( $result ); +} + +sub move +{ + my $self = shift; + my $dir = shift; + my $panStep = shift; + my $tiltStep = shift; + my $cmd = "PanSingleMoveDegree=$panStep&TiltSingleMoveDegree=$tiltStep&PanTiltSingleMove=$dir"; + $self->sendCmd( $cmd, 'pantiltcontrol' ); +} + +sub moveRel +{ + my $self = shift; + my $params = shift; + my $panStep = $self->getParam($params, 'panstep', 0); + my $tiltStep = $self->getParam($params, 'tiltstep', 0); + my $dir = shift; + $self->move( $dir, $panStep, $tiltStep ); +} + +sub moveRelUpLeft +{ + my $self = shift; + my $params = shift; + $self->moveRel( $params, 0 ); +} + +sub moveRelUp +{ + my $self = shift; + my $params = shift; + $self->moveRel( $params, 1 ); +} + +sub moveRelUpRight +{ + my $self = shift; + my $params = shift; + $self->moveRel( $params, 2 ); +} + +sub moveRelLeft +{ + my $self = shift; + my $params = shift; + $self->moveRel( $params, 3 ); +} + +sub moveRelRight +{ + my $self = shift; + my $params = shift; + $self->moveRel( $params, 5 ); +} + +sub moveRelDownLeft +{ + my $self = shift; + my $params = shift; + $self->moveRel( $params, 6 ); +} + +sub moveRelDown +{ + my $self = shift; + my $params = shift; + $self->moveRel( $params, 7 ); +} + +sub moveRelDownRight +{ + my $self = shift; + my $params = shift; + $self->moveRel( $params, 8 ); +} + +# moves the camera to center on the point that the user clicked on in the video image. +# This isn't extremely accurate but good enough for most purposes +sub moveMap +{ + # if the camera moves too much or too little, try increasing or decreasing this value + my $f = 11; + + my $self = shift; + my $params = shift; + my $xcoord = $self->getParam( $params, 'xcoord' ); + my $ycoord = $self->getParam( $params, 'ycoord' ); + + my $hor = $xcoord * 100 / $self->{Monitor}->{Width}; + my $ver = $ycoord * 100 / $self->{Monitor}->{Height}; + + my $direction; + my $horSteps; + my $verSteps; + if ($hor < 50 && $ver < 50) { + # up left + $horSteps = (50 - $hor) / $f; + $verSteps = (50 - $ver) / $f; + $direction = 0; + } elsif ($hor >= 50 && $ver < 50) { + # up right + $horSteps = ($hor - 50) / $f; + $verSteps = (50 - $ver) / $f; + $direction = 2; + } elsif ($hor < 50 && $ver >= 50) { + # down left + $horSteps = (50 - $hor) / $f; + $verSteps = ($ver - 50) / $f; + $direction = 6; + } elsif ($hor >= 50 && $ver >= 50) { + # down right + $horSteps = ($hor - 50) / $f; + $verSteps = ($ver - 50) / $f; + $direction = 8; + } + my $v = int($verSteps + .5); + my $h = int($horSteps + .5); + Debug( "Move Map to $xcoord,$ycoord, hor=$h, ver=$v with direction $direction" ); + $self->move( $direction, $h, $v ); +} + +sub presetClear +{ + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + Debug( "Clear Preset $preset" ); + my $cmd = "ClearPosition=$preset"; + $self->sendCmd( $cmd, 'pantiltcontrol' ); +} + +sub presetSet +{ + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + Debug( "Set Preset $preset" ); + my $cmd = "SetCurrentPosition=$preset&SetName=preset_$preset"; + $self->sendCmd( $cmd, 'pantiltcontrol' ); +} + +sub presetGoto +{ + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + Debug( "Goto Preset $preset" ); + my $cmd = "PanTiltPresetPositionMove=$preset"; + $self->sendCmd( $cmd, 'pantiltcontrol' ); +} + +sub presetHome +{ + my $self = shift; + Debug( "Home Preset" ); + $self->move( 4, 0, 0 ); +} + + +# IR Controls +# +# wake = IR on +# sleep = IR off +# reset = IR auto + +sub setDayNightMode { + my $self = shift; + my $mode = shift; + my $cmd = "DayNightMode=$mode&ConfigReboot=No"; + $self->sendCmd( $cmd, 'daynight' ); +} + +sub wake +{ + my $self = shift; + Debug( "Wake - IR on" ); + $self->setDayNightMode(2); +} + +sub sleep +{ + my $self = shift; + Debug( "Sleep - IR off" ); + $self->setDayNightMode(3); +} + +sub reset +{ + my $self = shift; + Debug( "Reset - IR auto" ); + $self->setDayNightMode(0); +} + +1; +__END__ +# Below is stub documentation for your module. You'd better edit it! + +=head1 NAME + +ZoneMinder::Database - Perl extension for DCS-5020L + +=head1 SYNOPSIS + + use ZoneMinder::Database; + DLINK DCS-5020L + +=head1 DESCRIPTION + +ZoneMinder driver for the D-Link consumer camera DCS-5020L. + +=head2 EXPORT + +None by default. + + + +=head1 SEE ALSO + +See if there are better instructions for the DCS-5020L at +http://www.zoneminder.com/wiki/index.php/Dlink + +=head1 AUTHOR + +Art Scheel ascheel (at) gmail + +=head1 COPYRIGHT AND LICENSE + +LGPLv3 + +=cut From 258ae23fb9a1ce5fa9d276a78a4de2f37638d8c0 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 19 Feb 2020 09:42:54 -0500 Subject: [PATCH 8/9] out an error if name contains invalid characters --- scripts/zmcamtool.pl.in | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/scripts/zmcamtool.pl.in b/scripts/zmcamtool.pl.in index 7e345b79d..3d68b1408 100644 --- a/scripts/zmcamtool.pl.in +++ b/scripts/zmcamtool.pl.in @@ -352,9 +352,13 @@ sub exportsql { } my $name = $ARGV[0]; - if ($name && $name =~ /^([A-Za-z0-9 ,.&()\/\-]+)$/) { # Allow alphanumeric and " ,.&()/-" - $name = $1; - $command .= qq( --where="Name = '$name'"); + if ( $name ) { + if ( $name =~ /^([A-Za-z0-9 ,.&()\/\-]+)$/ ) { # Allow alphanumeric and " ,.&()/-" + $name = $1; + $command .= qq( --where="Name = '$name'"); + } else { + print "Invalid characters in Name\n"; + } } $command .= " zm Controls MonitorPresets"; From ea2eba4110af76376c1bff308290ab84ae371033 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 19 Feb 2020 13:50:27 -0500 Subject: [PATCH 9/9] bump version to 1.34.3 --- db/zm_update-1.34.3.sql | 5 +++++ distros/redhat/zoneminder.spec | 2 +- version | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 db/zm_update-1.34.3.sql diff --git a/db/zm_update-1.34.3.sql b/db/zm_update-1.34.3.sql new file mode 100644 index 000000000..b84207047 --- /dev/null +++ b/db/zm_update-1.34.3.sql @@ -0,0 +1,5 @@ +-- +-- This updates a 1.34.2 database to 1.34.3 +-- +-- No changes required +-- diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index f36b239a8..7297648a9 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -28,7 +28,7 @@ %global _hardened_build 1 Name: zoneminder -Version: 1.34.2 +Version: 1.34.3 Release: 1%{?dist} Summary: A camera monitoring and analysis tool Group: System Environment/Daemons diff --git a/version b/version index 00e952d04..7e3856fe8 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.34.2 +1.34.3