Merge branch 'storageareas' into zma_to_thread

This commit is contained in:
Isaac Connor 2018-03-03 13:28:01 -08:00
commit 03d3f5e665
33 changed files with 1744 additions and 642 deletions

View File

@ -23,7 +23,17 @@ addons:
- curl
- sshfs
- sed
- binfmt-support
- qemu
- qemu-user-static
install:
- update-binfmts --enable qemu-arm
env:
global:
- DEB_BUILD_OPTIONS="parallel=4"
- DEBUILD_LINTIAN="no"
- SMPFLAGS="-j4"
matrix:
- OS=el DIST=6
- OS=el DIST=6 ARCH=i386 DOCKER_REPO=knnniggett/packpack
@ -34,6 +44,8 @@ env:
- OS=ubuntu DIST=xenial
- OS=ubuntu DIST=trusty ARCH=i386
- OS=ubuntu DIST=xenial ARCH=i386
- OS=raspbian DIST=stretch ARCH=armhf DOCKER_REPO=knnniggett/packpack
compiler:
- gcc
services:

View File

@ -58,6 +58,7 @@ if(NOT HOST_OS)
"ZoneMinder was unable to deterimine the host OS. Please report this. Value of CMAKE_SYSTEM_NAME: ${CMAKE_SYSTEM_NAME}")
endif(NOT HOST_OS)
set (CMAKE_CXX_STANDARD 11)
# Default CLFAGS and CXXFLAGS:
set(CMAKE_C_FLAGS_RELEASE "-Wall -D__STDC_CONSTANT_MACROS -O2")
set(CMAKE_CXX_FLAGS_RELEASE "-Wall -D__STDC_CONSTANT_MACROS -O2")

View File

@ -11,3 +11,6 @@ install(FILES ${dbfileslist} DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/db
# install zm_create.sql
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zm_create.sql" DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/db")
# install triggers.sql
install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/triggers.sql" DESTINATION "${CMAKE_INSTALL_DATADIR}/zoneminder/db")

View File

@ -228,37 +228,6 @@ CREATE TABLE `Events_Hour` (
KEY `Events_Hour_StartTime_idx` (`StartTime`)
) ENGINE=@ZM_MYSQL_ENGINE@;
delimiter //
DROP TRIGGER IF EXISTS Events_Hour_delete_trigger//
CREATE TRIGGER Events_Hour_delete_trigger BEFORE DELETE ON Events_Hour
FOR EACH ROW BEGIN
UPDATE Monitors SET
HourEvents = COALESCE(HourEvents,1)-1,
HourEventDiskSpace=COALESCE(HourEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0)
WHERE Id=OLD.MonitorId;
END;
//
DROP TRIGGER IF EXISTS Events_Hour_update_trigger//
CREATE TRIGGER Events_Hour_update_trigger AFTER UPDATE ON Events_Hour
FOR EACH ROW
BEGIN
declare diff BIGINT default 0;
set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0);
IF ( diff ) THEN
IF ( NEW.MonitorID != OLD.MonitorID ) THEN
UPDATE Monitors SET HourEventDiskSpace=COALESCE(HourEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0) WHERE Monitors.Id=OLD.MonitorId;
UPDATE Monitors SET HourEventDiskSpace=COALESCE(HourEventDiskSpace,0)-COALESCE(NEW.DiskSpace,0) WHERE Monitors.Id=NEW.MonitorId;
ELSE
UPDATE Monitors SET HourEventDiskSpace=COALESCE(HourEventDiskSpace,0)+diff WHERE Monitors.Id=NEW.MonitorId;
END IF;
END IF;
END;
//
DELIMITER ;
DROP TABLE IF EXISTS `Events_Day`;
CREATE TABLE `Events_Day` (
`EventId` int(10) unsigned NOT NULL,
@ -270,37 +239,6 @@ CREATE TABLE `Events_Day` (
KEY `Events_Day_StartTime_idx` (`StartTime`)
) ENGINE=@ZM_MYSQL_ENGINE@;
delimiter //
DROP TRIGGER IF EXISTS Events_Day_delete_trigger//
CREATE TRIGGER Events_Day_delete_trigger BEFORE DELETE ON Events_Day
FOR EACH ROW BEGIN
UPDATE Monitors SET
DayEvents = COALESCE(DayEvents,1)-1,
DayEventDiskSpace=COALESCE(DayEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0)
WHERE Id=OLD.MonitorId;
END;
//
DROP TRIGGER IF EXISTS Events_Day_update_trigger;
CREATE TRIGGER Events_Day_update_trigger AFTER UPDATE ON Events_Day
FOR EACH ROW
BEGIN
declare diff BIGINT default 0;
set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0);
IF ( diff ) THEN
IF ( NEW.MonitorID != OLD.MonitorID ) THEN
UPDATE Monitors SET DayEventDiskSpace=COALESCE(DayEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0) WHERE Monitors.Id=OLD.MonitorId;
UPDATE Monitors SET DayEventDiskSpace=COALESCE(DayEventDiskSpace,0)+COALESCE(NEW.DiskSpace,0) WHERE Monitors.Id=NEW.MonitorId;
ELSE
UPDATE Monitors SET DayEventDiskSpace=COALESCE(DayEventDiskSpace,0)+diff WHERE Monitors.Id=NEW.MonitorId;
END IF;
END IF;
END;
//
DELIMITER ;
DROP TABLE IF EXISTS `Events_Week`;
CREATE TABLE `Events_Week` (
`EventId` int(10) unsigned NOT NULL,
@ -312,37 +250,6 @@ CREATE TABLE `Events_Week` (
KEY `Events_Week_StartTime_idx` (`StartTime`)
) ENGINE=@ZM_MYSQL_ENGINE@;
delimiter //
DROP TRIGGER IF EXISTS Events_Week_delete_trigger//
CREATE TRIGGER Events_Week_delete_trigger BEFORE DELETE ON Events_Week
FOR EACH ROW BEGIN
UPDATE Monitors SET
WeekEvents = COALESCE(WeekEvents,1)-1,
WeekEventDiskSpace=COALESCE(WeekEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0)
WHERE Id=OLD.MonitorId;
END;
//
DROP TRIGGER IF EXISTS Events_Week_update_trigger;
CREATE TRIGGER Events_Week_update_trigger AFTER UPDATE ON Events_Week
FOR EACH ROW
BEGIN
declare diff BIGINT default 0;
set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0);
IF ( diff ) THEN
IF ( NEW.MonitorID != OLD.MonitorID ) THEN
UPDATE Monitors SET WeekEventDiskSpace=COALESCE(WeekEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0) WHERE Monitors.Id=OLD.MonitorId;
UPDATE Monitors SET WeekEventDiskSpace=COALESCE(WeekEventDiskSpace,0)+COALESCE(NEW.DiskSpace,0) WHERE Monitors.Id=NEW.MonitorId;
ELSE
UPDATE Monitors SET WeekEventDiskSpace=COALESCE(WeekEventDiskSpace,0)+diff WHERE Monitors.Id=NEW.MonitorId;
END IF;
END IF;
END;
//
DELIMITER ;
DROP TABLE IF EXISTS `Events_Month`;
CREATE TABLE `Events_Month` (
`EventId` int(10) unsigned NOT NULL,
@ -354,38 +261,6 @@ CREATE TABLE `Events_Month` (
KEY `Events_Month_StartTime_idx` (`StartTime`)
) ENGINE=@ZM_MYSQL_ENGINE@;
delimiter //
DROP TRIGGER IF EXISTS Events_Month_delete_trigger//
CREATE TRIGGER Events_Month_delete_trigger BEFORE DELETE ON Events_Month
FOR EACH ROW BEGIN
UPDATE Monitors SET
MonthEvents = COALESCE(MonthEvents,1)-1,
MonthEventDiskSpace=COALESCE(MonthEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0)
WHERE Id=OLD.MonitorId;
END;
//
DROP TRIGGER IF EXISTS Events_Month_update_trigger;
CREATE TRIGGER Events_Month_update_trigger AFTER UPDATE ON Events_Month
FOR EACH ROW
BEGIN
declare diff BIGINT default 0;
set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0);
IF ( diff ) THEN
IF ( NEW.MonitorID != OLD.MonitorID ) THEN
UPDATE Monitors SET MonthEventDiskSpace=COALESCE(MonthEventDiskSpace,0)-COALESCE(OLD.DiskSpace) WHERE Monitors.Id=OLD.MonitorId;
UPDATE Monitors SET MonthEventDiskSpace=COALESCE(MonthEventDiskSpace,0)+COALESCE(NEW.DiskSpace) WHERE Monitors.Id=NEW.MonitorId;
ELSE
UPDATE Monitors SET MonthEventDiskSpace=COALESCE(MonthEventDiskSpace,0)+diff WHERE Monitors.Id=NEW.MonitorId;
END IF;
END IF;
END;
//
DELIMITER ;
DROP TABLE IF EXISTS `Events_Archived`;
CREATE TABLE `Events_Archived` (
@ -396,135 +271,6 @@ CREATE TABLE `Events_Archived` (
KEY `Events_Archived_MonitorId_idx` (`MonitorId`)
) ENGINE=@ZM_MYSQL_ENGINE@;
drop procedure if exists update_storage_stats;
delimiter //
create procedure update_storage_stats(IN StorageId smallint(5), IN space BIGINT)
sql security invoker
deterministic
begin
update Storage set DiskSpace = COALESCE(DiskSpace,0) + COALESCE(space,0) where Id = StorageId;
end;
//
drop trigger if exists event_update_trigger//
CREATE TRIGGER event_update_trigger AFTER UPDATE ON Events
FOR EACH ROW
BEGIN
declare diff BIGINT default 0;
set diff = COALESCE(NEW.DiskSpace,0) - COALESCE(OLD.DiskSpace,0);
IF ( NEW.StorageId = OLD.StorageID ) THEN
IF ( diff ) THEN
call update_storage_stats(OLD.StorageId, diff);
END IF;
ELSE
IF ( NEW.DiskSpace ) THEN
call update_storage_stats(NEW.StorageId, NEW.DiskSpace);
END IF;
IF ( OLD.DiskSpace ) THEN
call update_storage_stats(OLD.StorageId, -OLD.DiskSpace);
END IF;
END IF;
UPDATE Events_Hour SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id;
UPDATE Events_Day SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id;
UPDATE Events_Week SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id;
UPDATE Events_Month SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id;
IF ( NEW.Archived != OLD.Archived ) THEN
IF ( NEW.Archived ) THEN
INSERT INTO Events_Archived (EventId,MonitorId,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.DiskSpace);
UPDATE Monitors SET ArchivedEvents = COALESCE(ArchivedEvents,0)+1, ArchivedEventDiskSpace = COALESCE(ArchivedEventDiskSpace,0) + COALESCE(NEW.DiskSpace,0) WHERE Id=NEW.MonitorId;
ELSEIF ( OLD.Archived ) THEN
DELETE FROM Events_Archived WHERE EventId=OLD.Id;
UPDATE Monitors SET ArchivedEvents = COALESCE(ArchivedEvents,0)-1, ArchivedEventDiskSpace = COALESCE(ArchivedEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0) WHERE Id=OLD.MonitorId;
ELSE
IF ( OLD.DiskSpace != NEW.DiskSpace ) THEN
UPDATE Events_Archived SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id;
UPDATE Monitors SET
ArchivedEventDiskSpace = COALESCE(ArchivedEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0) + COALESCE(NEW.DiskSpace,0)
WHERE Id=OLD.MonitorId;
END IF;
END IF;
ELSE IF ( NEW.Archived AND diff ) THEN
UPDATE Events_Archived SET DiskSpace=NEW.DiskSpace WHERE EventId=NEW.Id;
END IF;
IF ( diff ) THEN
UPDATE Monitors SET TotalEventDiskSpace = COALESCE(TotalEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0) + COALESCE(NEW.DiskSpace,0) WHERE Id=OLD.MonitorId;
END IF;
END;
//
delimiter ;
DROP TRIGGER IF EXISTS event_insert_trigger;
delimiter //
/* The assumption is that when an Event is inserted, it has no size yet, so don't bother updating the DiskSpace, just the count.
* The DiskSpace will get update in the Event Update Trigger
*/
CREATE TRIGGER event_insert_trigger AFTER INSERT ON Events
FOR EACH ROW
BEGIN
INSERT INTO Events_Hour (EventId,MonitorId,StartTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartTime,0);
INSERT INTO Events_Day (EventId,MonitorId,StartTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartTime,0);
INSERT INTO Events_Week (EventId,MonitorId,StartTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartTime,0);
INSERT INTO Events_Month (EventId,MonitorId,StartTime,DiskSpace) VALUES (NEW.Id,NEW.MonitorId,NEW.StartTime,0);
UPDATE Monitors SET
HourEvents = COALESCE(DayEvents,0)+1,
DayEvents = COALESCE(DayEvents,0)+1,
WeekEvents = COALESCE(DayEvents,0)+1,
MonthEvents = COALESCE(DayEvents,0)+1,
TotalEvents = COALESCE(TotalEvents,0)+1
WHERE Id=NEW.MonitorId;
END;
//
DROP TRIGGER IF EXISTS event_delete_trigger//
CREATE TRIGGER event_delete_trigger BEFORE DELETE ON Events
FOR EACH ROW
BEGIN
IF ( OLD.DiskSpace ) THEN
call update_storage_stats(OLD.StorageId, -OLD.DiskSpace);
END IF;
DELETE FROM Events_Hour WHERE EventId=OLD.Id;
DELETE FROM Events_Day WHERE EventId=OLD.Id;
DELETE FROM Events_Week WHERE EventId=OLD.Id;
DELETE FROM Events_Month WHERE EventId=OLD.Id;
IF ( OLD.Archived ) THEN
DELETE FROM Events_Archived WHERE EventId=OLD.Id;
UPDATE Monitors SET
ArchivedEvents = COALESCE(ArchivedEvents,1) - 1,
ArchivedEventDiskSpace = COALESCE(ArchivedEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0),
TotalEvents = COALESCE(TotalEvents,1) - 1,
TotalEventDiskSpace = COALESCE(TotalEventDiskSpace,0) - COALESCE(OLD.DiskSpace,0)
WHERE Id=OLD.MonitorId;
ELSE
UPDATE Monitors SET
TotalEvents = COALESCE(TotalEvents,1)-1,
TotalEventDiskSpace=COALESCE(TotalEventDiskSpace,0)-COALESCE(OLD.DiskSpace,0)
WHERE Id=OLD.MonitorId;
END IF;
END;
//
delimiter ;
--
-- Table structure for table `Filters`
--
@ -950,24 +696,6 @@ CREATE TABLE `Zones` (
KEY `MonitorId` (`MonitorId`)
) ENGINE=@ZM_MYSQL_ENGINE@;
DELIMITER //
DROP TRIGGER IF EXISTS Zone_Insert_Trigger//
CREATE TRIGGER Zone_Insert_Trigger AFTER INSERT ON Zones
FOR EACH ROW
BEGIN
UPDATE Monitors SET ZoneCount=(SELECT COUNT(*) FROM Zones WHERE MonitorId=NEW.MonitorId) WHERE Id=NEW.MonitorID;
END
//
DROP TRIGGER IF EXISTS Zone_Delete_Trigger//
CREATE TRIGGER Zone_Delete_Trigger AFTER DELETE ON Zones
FOR EACH ROW
BEGIN
UPDATE Monitors SET ZoneCount=(SELECT COUNT(*) FROM Zones WHERE MonitorId=OLD.MonitorId) WHERE Id=OLD.MonitorID;
END
//
DELIMITER ;
DROP TABLE IF EXISTS `Storage`;
CREATE TABLE `Storage` (
`Id` smallint(5) unsigned NOT NULL auto_increment,
@ -1041,6 +769,9 @@ INSERT INTO `Controls` VALUES (NULL,'Keekoon','Remote','Keekoon', 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,'Floureon 1080P','Ffmpeg','Floureon',0,0,0,1,0,0,0,1,1,18,1,1,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,1,1,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,1,20,0,1,1,1,0,0,0,1,1,0,0,0,0,1,1,8,0,0,1,0,0,0,0,1,1,8,0,0,0,0);
INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-423','Ffmpeg','Reolink',0,0,1,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,1,64,1,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,'Reolink RLC-411','Ffmpeg','Reolink',0,0,1,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);
--
-- Add some monitor preset values
@ -1139,7 +870,7 @@ CREATE TABLE Maps (
PRIMARY KEY (`Id`)
);
DROP TABLE IF EXISTS MontageLayout;
DROP TABLE IF EXISTS MontageLayouts;
CREATE TABLE MontageLayouts (
`Id` int(10) unsigned NOT NULL auto_increment,

View File

@ -5,6 +5,9 @@ set -e
if [ "$1" = "configure" ]; then
. /etc/zm/zm.conf
for i in /etc/zm/conf.d/*.conf; do
. $i
done;
# The logs can contain passwords, etc... so by setting group root, only www-data can read them, not people in the www-data group.
chown www-data:root /var/log/zm

View File

@ -5,6 +5,10 @@ set -e
if [ "$1" = "configure" ]; then
. /etc/zm/zm.conf
for i in /etc/zm/conf.d/*.conf; do
. $i
done;
# The logs can contain passwords, etc... so by setting group root, only www-data can read them, not people in the www-data group
chown www-data:root /var/log/zm

View File

@ -16,7 +16,7 @@ Build-Depends: debhelper (>= 9), dh-systemd, python-sphinx | python3-sphinx, apa
,libcurl4-gnutls-dev
,libgnutls-openssl-dev
,libjpeg8-dev | libjpeg9-dev | libjpeg62-turbo-dev
,libmysqlclient-dev | libmariadbclient-dev
,default-libmysqlclient-dev | libmysqlclient-dev | libmariadbclient-dev
,libpcre3-dev
,libpolkit-gobject-1-dev
,libv4l-dev (>= 0.8.3) [!hurd-any]

View File

@ -5,6 +5,9 @@ set -e
if [ "$1" = "configure" ]; then
. /etc/zm/zm.conf
for i in /etc/zm/conf.d/*.conf; do
. $i
done;
# The logs can contain passwords, etc... so by setting group root, only www-data can read them, not people in the www-data group
chown www-data:root /var/log/zm

View File

@ -44,7 +44,7 @@ Clone the ZoneMinder project if you have not done so already.
::
git clone https://ZoneMinder/ZoneMinder
git clone https://github.com/ZoneMinder/ZoneMinder
cd ZoneMinder
Alternatively, if you have already cloned the repo and wish to update it, do the following.

View File

@ -0,0 +1,630 @@
# ==========================================================================
#
# ZoneMinder Reolink IP Control Protocol Module, Date: 2016-01-19
# Converted for use with Reolink IP Camera by Chris Swertfeger
# Copyright (C) 2016 Chris Swertfeger
#
# 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 first implementation of the Reolink IP camera control
# protocol
#
package ZoneMinder::Control::Reolink;
use 5.006;
use strict;
use warnings;
require ZoneMinder::Base;
require ZoneMinder::Control;
our @ISA = qw(ZoneMinder::Control);
our %CamParams = ();
# ==========================================================================
#
# Reolink IP Control Protocol
# This script sends ONVIF compliant commands and may work with other cameras
# that require authentication
#
# The script was developed against a RLC-423 and RLC-420.
#
# Basic preset functions are supported, but more advanced features, which make
# use of abnormally high preset numbers (ir lamp control, tours, pan speed, etc)
# may or may not work.
#
#
# On ControlAddress use the format :
# USERNAME:PASSWORD@ADDRESS:PORT
# eg : admin:pass@10.1.2.1:8899
# admin:password@10.0.100.1:8899
#
# Use port 8000 by default for Reolink cameras
#
# Make sure and place a value in the Auto Stop Timeout field.
# Recommend starting with a value of 1 second, and adjust accordingly.
#
# ==========================================================================
use ZoneMinder::Logger qw(:all);
use ZoneMinder::Config qw(:all);
use Time::HiRes qw( usleep );
use MIME::Base64;
use Digest::SHA;
use DateTime;
my ($username,$password,$host,$port);
sub new
{
my $class = shift;
my $id = shift;
my $self = ZoneMinder::Control->new( $id );
my $logindetails = "";
bless( $self, $class );
srand( time() );
return $self;
}
our $AUTOLOAD;
sub AUTOLOAD
{
my $self = shift;
my $class = ref( ) || 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();
#
# Extract the username/password host/port from ControlAddress
#
if( $self->{Monitor}{ControlAddress} =~ /^([^:]+):([^@]+)@(.+)/ )
{ # user:pass@host...
$username = $1;
$password = $2;
$host = $3;
}
elsif( $self->{Monitor}{ControlAddress} =~ /^([^@]+)@(.+)/ )
{ # user@host...
$username = $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;
}
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 $msg = shift;
my $content_type = shift;
my $result = undef;
printMsg( $cmd, "Tx" );
my $server_endpoint = "http://".$host.":".$port."/$cmd";
my $req = HTTP::Request->new( POST => $server_endpoint );
$req->header('content-type' => $content_type);
$req->header('Host' => $host.":".$port);
$req->header('content-length' => length($msg));
$req->header('accept-encoding' => 'gzip, deflate');
$req->header('connection' => 'close');
$req->content($msg);
my $res = $self->{ua}->request($req);
if ( $res->is_success ) {
$result = !undef;
} else {
Error( "After sending PTZ command, camera returned the following error:'".$res->status_line()."'" );
}
return( $result );
}
sub getCamParams
{
my $self = shift;
my $nonce;
for (0..20){$nonce .= chr(int(rand(254)));}
my $mydate = DateTime->now()->iso8601().'Z';
my $sha = Digest::SHA->new(1);
$sha->add($nonce.$mydate.$password);
my $digest = encode_base64($sha->digest,"");
my $msg = '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Header><Security s:mustUnderstand="1" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><UsernameToken><Username>'.$username.'</Username><Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">'.$digest.'</Password><Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">'.encode_base64($nonce,"").'</Nonce><Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">'.$mydate.'</Created></UsernameToken></Security></s:Header><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><GetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken></GetImagingSettings></s:Body></s:Envelope>';
my $server_endpoint = "http://".$self->{Monitor}->{ControlAddress}."/onvif/imaging";
my $req = HTTP::Request->new( POST => $server_endpoint );
$req->header('content-type' => 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/GetImagingSettings"');
$req->header('Host' => $host.":".$port);
$req->header('content-length' => length($msg));
$req->header('accept-encoding' => 'gzip, deflate');
$req->header('connection' => 'Close');
$req->content($msg);
my $res = $self->{ua}->request($req);
if ( $res->is_success ) {
# We should really use an xml or soap library to parse the xml tags
my $content = $res->decoded_content;
if ($content =~ /.*<tt:(Brightness)>(.+)<\/tt:Brightness>.*/) {
$CamParams{$1} = $2;
}
if ($content =~ /.*<tt:(Contrast)>(.+)<\/tt:Contrast>.*/) {
$CamParams{$1} = $2;
}
}
else
{
Error( "Unable to retrieve camera image settings:'".$res->status_line()."'" );
}
}
#autoStop
#This makes use of the ZoneMinder Auto Stop Timeout on the Control Tab
sub autoStop
{
my $self = shift;
my $autostop = shift;
if( $autostop ) {
Debug( "Auto Stop" );
my $cmd = 'onvif/PTZ';
my $nonce;
for (0..20){$nonce .= chr(int(rand(254)));}
my $mydate = DateTime->now()->iso8601().'Z';
my $sha = Digest::SHA->new(1);
$sha->add($nonce.$mydate.$password);
my $digest = encode_base64($sha->digest,"");
my $msg = '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Header><Security s:mustUnderstand="1" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><UsernameToken><Username>'.$username.'</Username><Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">'.$digest.'</Password><Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">'.encode_base64($nonce,"").'</Nonce><Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">'.$mydate.'</Created></UsernameToken></Security></s:Header><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><Stop xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><PanTilt>true</PanTilt><Zoom>false</Zoom></Stop></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
usleep( $autostop );
$self->sendCmd( $cmd, $msg, $content_type );
}
}
# Reset the Camera
sub reset
{
Debug( "Camera Reset" );
my $self = shift;
my $nonce;
for (0..20){$nonce .= chr(int(rand(254)));}
my $mydate = DateTime->now()->iso8601().'Z';
my $sha = Digest::SHA->new(1);
$sha->add($nonce.$mydate.$password);
my $digest = encode_base64($sha->digest,"");
my $cmd = "";
my $msg = '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Header><Security s:mustUnderstand="1" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><UsernameToken><Username>'.$username.'</Username><Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">'.$digest.'</Password><Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">'.encode_base64($nonce,"").'</Nonce><Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">'.$mydate.'</Created></UsernameToken></Security></s:Header><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SystemReboot xmlns="http://www.onvif.org/ver10/device/wsdl"/></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver10/device/wsdl/SystemReboot"';
$self->sendCmd( $cmd, $msg, $content_type );
}
#Up Arrow
sub moveConUp
{
Debug( "Move Up" );
my $self = shift;
my $cmd = 'onvif/PTZ';
my $nonce;
for (0..20){$nonce .= chr(int(rand(254)));}
my $mydate = DateTime->now()->iso8601().'Z';
my $sha = Digest::SHA->new(1);
$sha->add($nonce.$mydate.$password);
my $digest = encode_base64($sha->digest,"");
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Header><Security s:mustUnderstand="1" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><UsernameToken><Username>'.$username.'</Username><Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">'.$digest.'</Password><Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">'.encode_base64($nonce,"").'</Nonce><Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">'.$mydate.'</Created></UsernameToken></Security></s:Header><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="0" y="0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd( $cmd, $msg, $content_type );
$self->autoStop( $self->{Monitor}->{AutoStopTimeout} );
}
#Down Arrow
sub moveConDown
{
Debug( "Move Down" );
my $self = shift;
my $cmd = 'onvif/PTZ';
my $nonce;
for (0..20){$nonce .= chr(int(rand(254)));}
my $mydate = DateTime->now()->iso8601().'Z';
my $sha = Digest::SHA->new(1);
$sha->add($nonce.$mydate.$password);
my $digest = encode_base64($sha->digest,"");
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Header><Security s:mustUnderstand="1" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><UsernameToken><Username>'.$username.'</Username><Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">'.$digest.'</Password><Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">'.encode_base64($nonce,"").'</Nonce><Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">'.$mydate.'</Created></UsernameToken></Security></s:Header><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="0" y="-0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd( $cmd, $msg, $content_type );
$self->autoStop( $self->{Monitor}->{AutoStopTimeout} );
}
#Left Arrow
sub moveConLeft
{
Debug( "Move Left" );
my $self = shift;
my $cmd = 'onvif/PTZ';
my $nonce;
for (0..20){$nonce .= chr(int(rand(254)));}
my $mydate = DateTime->now()->iso8601().'Z';
my $sha = Digest::SHA->new(1);
$sha->add($nonce.$mydate.$password);
my $digest = encode_base64($sha->digest,"");
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Header><Security s:mustUnderstand="1" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><UsernameToken><Username>'.$username.'</Username><Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">'.$digest.'</Password><Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">'.encode_base64($nonce,"").'</Nonce><Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">'.$mydate.'</Created></UsernameToken></Security></s:Header><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="-0.49" y="0" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd( $cmd, $msg, $content_type );
$self->autoStop( $self->{Monitor}->{AutoStopTimeout} );
}
#Right Arrow
sub moveConRight
{
Debug( "Move Right" );
my $self = shift;
my $cmd = 'onvif/PTZ';
my $nonce;
for (0..20){$nonce .= chr(int(rand(254)));}
my $mydate = DateTime->now()->iso8601().'Z';
my $sha = Digest::SHA->new(1);
$sha->add($nonce.$mydate.$password);
my $digest = encode_base64($sha->digest,"");
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Header><Security s:mustUnderstand="1" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><UsernameToken><Username>'.$username.'</Username><Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">'.$digest.'</Password><Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">'.encode_base64($nonce,"").'</Nonce><Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">'.$mydate.'</Created></UsernameToken></Security></s:Header><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="0.49" y="0" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd( $cmd, $msg, $content_type );
$self->autoStop( $self->{Monitor}->{AutoStopTimeout} );
}
#Zoom In
sub zoomConTele
{
Debug( "Zoom Tele" );
my $self = shift;
my $cmd = 'onvif/PTZ';
my $nonce;
for (0..20){$nonce .= chr(int(rand(254)));}
my $mydate = DateTime->now()->iso8601().'Z';
my $sha = Digest::SHA->new(1);
$sha->add($nonce.$mydate.$password);
my $digest = encode_base64($sha->digest,"");
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Header><Security s:mustUnderstand="1" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><UsernameToken><Username>'.$username.'</Username><Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">'.$digest.'</Password><Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">'.encode_base64($nonce,"").'</Nonce><Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">'.$mydate.'</Created></UsernameToken></Security></s:Header><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><Zoom x="0.49" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd( $cmd, $msg, $content_type );
$self->autoStop( $self->{Monitor}->{AutoStopTimeout} );
}
#Zoom Out
sub zoomConWide
{
Debug( "Zoom Wide" );
my $self = shift;
my $cmd = 'onvif/PTZ';
my $nonce;
for (0..20){$nonce .= chr(int(rand(254)));}
my $mydate = DateTime->now()->iso8601().'Z';
my $sha = Digest::SHA->new(1);
$sha->add($nonce.$mydate.$password);
my $digest = encode_base64($sha->digest,"");
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Header><Security s:mustUnderstand="1" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><UsernameToken><Username>'.$username.'</Username><Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">'.$digest.'</Password><Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">'.encode_base64($nonce,"").'</Nonce><Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">'.$mydate.'</Created></UsernameToken></Security></s:Header><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><Zoom x="-0.49" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd( $cmd, $msg, $content_type );
$self->autoStop( $self->{Monitor}->{AutoStopTimeout} );
}
#Diagonally Up Right Arrow
#This camera does not have builtin diagonal commands so we emulate them
sub moveConUpRight
{
Debug( "Move Diagonally Up Right" );
my $self = shift;
my $cmd = 'onvif/PTZ';
my $nonce;
for (0..20){$nonce .= chr(int(rand(254)));}
my $mydate = DateTime->now()->iso8601().'Z';
my $sha = Digest::SHA->new(1);
$sha->add($nonce.$mydate.$password);
my $digest = encode_base64($sha->digest,"");
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Header><Security s:mustUnderstand="1" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><UsernameToken><Username>'.$username.'</Username><Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">'.$digest.'</Password><Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">'.encode_base64($nonce,"").'</Nonce><Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">'.$mydate.'</Created></UsernameToken></Security></s:Header><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="0.5" y="0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd( $cmd, $msg, $content_type );
$self->autoStop( $self->{Monitor}->{AutoStopTimeout} );
}
#Diagonally Down Right Arrow
#This camera does not have builtin diagonal commands so we emulate them
sub moveConDownRight
{
Debug( "Move Diagonally Down Right" );
my $self = shift;
my $cmd = 'onvif/PTZ';
my $nonce;
for (0..20){$nonce .= chr(int(rand(254)));}
my $mydate = DateTime->now()->iso8601().'Z';
my $sha = Digest::SHA->new(1);
$sha->add($nonce.$mydate.$password);
my $digest = encode_base64($sha->digest,"");
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Header><Security s:mustUnderstand="1" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><UsernameToken><Username>'.$username.'</Username><Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">'.$digest.'</Password><Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">'.encode_base64($nonce,"").'</Nonce><Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">'.$mydate.'</Created></UsernameToken></Security></s:Header><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="0.5" y="-0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd( $cmd, $msg, $content_type );
$self->autoStop( $self->{Monitor}->{AutoStopTimeout} );
}
#Diagonally Up Left Arrow
#This camera does not have builtin diagonal commands so we emulate them
sub moveConUpLeft
{
Debug( "Move Diagonally Up Left" );
my $self = shift;
my $cmd = 'onvif/PTZ';
my $nonce;
for (0..20){$nonce .= chr(int(rand(254)));}
my $mydate = DateTime->now()->iso8601().'Z';
my $sha = Digest::SHA->new(1);
$sha->add($nonce.$mydate.$password);
my $digest = encode_base64($sha->digest,"");
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Header><Security s:mustUnderstand="1" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><UsernameToken><Username>'.$username.'</Username><Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">'.$digest.'</Password><Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">'.encode_base64($nonce,"").'</Nonce><Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">'.$mydate.'</Created></UsernameToken></Security></s:Header><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="-0.5" y="0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd( $cmd, $msg, $content_type );
$self->autoStop( $self->{Monitor}->{AutoStopTimeout} );
}
#Diagonally Down Left Arrow
#This camera does not have builtin diagonal commands so we emulate them
sub moveConDownLeft
{
Debug( "Move Diagonally Down Left" );
my $self = shift;
my $cmd = 'onvif/PTZ';
my $nonce;
for (0..20){$nonce .= chr(int(rand(254)));}
my $mydate = DateTime->now()->iso8601().'Z';
my $sha = Digest::SHA->new(1);
$sha->add($nonce.$mydate.$password);
my $digest = encode_base64($sha->digest,"");
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Header><Security s:mustUnderstand="1" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><UsernameToken><Username>'.$username.'</Username><Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">'.$digest.'</Password><Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">'.encode_base64($nonce,"").'</Nonce><Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">'.$mydate.'</Created></UsernameToken></Security></s:Header><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><ContinuousMove xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><Velocity><PanTilt x="-0.5" y="-0.5" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd( $cmd, $msg, $content_type );
$self->autoStop( $self->{Monitor}->{AutoStopTimeout} );
}
#Stop
sub moveStop
{
Debug( "Move Stop" );
my $self = shift;
my $cmd = 'onvif/PTZ';
my $nonce;
for (0..20){$nonce .= chr(int(rand(254)));}
my $mydate = DateTime->now()->iso8601().'Z';
my $sha = Digest::SHA->new(1);
$sha->add($nonce.$mydate.$password);
my $digest = encode_base64($sha->digest,"");
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Header><Security s:mustUnderstand="1" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><UsernameToken><Username>'.$username.'</Username><Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">'.$digest.'</Password><Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">'.encode_base64($nonce,"").'</Nonce><Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">'.$mydate.'</Created></UsernameToken></Security></s:Header><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><Stop xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><PanTilt>true</PanTilt><Zoom>false</Zoom></Stop></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
$self->sendCmd( $cmd, $msg, $content_type );
}
#Set Camera Preset
sub presetSet
{
my $self = shift;
my $params = shift;
my $preset = $self->getParam( $params, 'preset' );
Debug( "Set Preset $preset" );
my $cmd = 'onvif/PTZ';
my $nonce;
for (0..20){$nonce .= chr(int(rand(254)));}
my $mydate = DateTime->now()->iso8601().'Z';
my $sha = Digest::SHA->new(1);
$sha->add($nonce.$mydate.$password);
my $digest = encode_base64($sha->digest,"");
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Header><Security s:mustUnderstand="1" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><UsernameToken><Username>'.$username.'</Username><Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">'.$digest.'</Password><Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">'.encode_base64($nonce,"").'</Nonce><Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">'.$mydate.'</Created></UsernameToken></Security></s:Header><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetPreset xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><PresetToken>'.$preset.'</PresetToken></SetPreset></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/SetPreset"';
$self->sendCmd( $cmd, $msg, $content_type );
}
#Recall Camera Preset
sub presetGoto
{
my $self = shift;
my $params = shift;
my $preset = $self->getParam( $params, 'preset' );
my $num = sprintf("%03d", $preset);
$num=~ tr/ /0/;
Debug( "Goto Preset $preset" );
my $cmd = 'onvif/PTZ';
my $nonce;
for (0..20){$nonce .= chr(int(rand(254)));}
my $mydate = DateTime->now()->iso8601().'Z';
my $sha = Digest::SHA->new(1);
$sha->add($nonce.$mydate.$password);
my $digest = encode_base64($sha->digest,"");
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Header><Security s:mustUnderstand="1" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><UsernameToken><Username>'.$username.'</Username><Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">'.$digest.'</Password><Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">'.encode_base64($nonce,"").'</Nonce><Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">'.$mydate.'</Created></UsernameToken></Security></s:Header><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><GotoPreset xmlns="http://www.onvif.org/ver20/ptz/wsdl"><ProfileToken>000</ProfileToken><PresetToken>'.$num.'</PresetToken></GotoPreset></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/GotoPreset"';
$self->sendCmd( $cmd, $msg, $content_type );
}
#Horizontal Patrol
#To be determined if this camera supports this feature
sub horizontalPatrol
{
Debug( "Horizontal Patrol" );
my $self = shift;
my $cmd = '';
my $msg ='';
my $content_type = '';
# $self->sendCmd( $cmd, $msg, $content_type );
Error( "PTZ Command not implemented in control script." );
}
#Horizontal Patrol Stop
#To be determined if this camera supports this feature
sub horizontalPatrolStop
{
Debug( "Horizontal Patrol Stop" );
my $self = shift;
my $cmd = '';
my $msg ='';
my $content_type = '';
# $self->sendCmd( $cmd, $msg, $content_type );
Error( "PTZ Command not implemented in control script." );
}
# Increase Brightness
sub irisAbsOpen
{
Debug( "Iris $CamParams{'Brightness'}" );
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'Brightness'});
my $step = $self->getParam( $params, 'step' );
my $max = 100;
$CamParams{'Brightness'} += $step;
$CamParams{'Brightness'} = $max if ($CamParams{'Brightness'} > $max);
my $cmd = 'onvif/imaging';
my $nonce;
for (0..20){$nonce .= chr(int(rand(254)));}
my $mydate = DateTime->now()->iso8601().'Z';
my $sha = Digest::SHA->new(1);
$sha->add($nonce.$mydate.$password);
my $digest = encode_base64($sha->digest,"");
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Header><Security s:mustUnderstand="1" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><UsernameToken><Username>'.$username.'</Username><Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">'.$digest.'</Password><Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">'.encode_base64($nonce,"").'</Nonce><Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">'.$mydate.'</Created></UsernameToken></Security></s:Header><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken><ImagingSettings><Brightness xmlns="http://www.onvif.org/ver10/schema">'.$CamParams{'Brightness'}.'</Brightness></ImagingSettings><ForcePersistence>true</ForcePersistence></SetImagingSettings></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"';
$self->sendCmd( $cmd, $msg, $content_type );
}
# Decrease Brightness
sub irisAbsClose
{
Debug( "Iris $CamParams{'Brightness'}" );
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'brightness'});
my $step = $self->getParam( $params, 'step' );
my $min = 0;
$CamParams{'Brightness'} -= $step;
$CamParams{'Brightness'} = $min if ($CamParams{'Brightness'} < $min);
my $cmd = 'onvif/imaging';
my $nonce;
for (0..20){$nonce .= chr(int(rand(254)));}
my $mydate = DateTime->now()->iso8601().'Z';
my $sha = Digest::SHA->new(1);
$sha->add($nonce.$mydate.$password);
my $digest = encode_base64($sha->digest,"");
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Header><Security s:mustUnderstand="1" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><UsernameToken><Username>'.$username.'</Username><Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">'.$digest.'</Password><Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">'.encode_base64($nonce,"").'</Nonce><Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">'.$mydate.'</Created></UsernameToken></Security></s:Header><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken><ImagingSettings><Brightness xmlns="http://www.onvif.org/ver10/schema">'.$CamParams{'Brightness'}.'</Brightness></ImagingSettings><ForcePersistence>true</ForcePersistence></SetImagingSettings></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"';
$self->sendCmd( $cmd, $msg, $content_type );
}
# Increase Contrast
sub whiteAbsIn
{
Debug( "Iris $CamParams{'Contrast'}" );
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'Contrast'});
my $step = $self->getParam( $params, 'step' );
my $max = 100;
$CamParams{'Contrast'} += $step;
$CamParams{'Contrast'} = $max if ($CamParams{'Contrast'} > $max);
my $cmd = 'onvif/imaging';
my $nonce;
for (0..20){$nonce .= chr(int(rand(254)));}
my $mydate = DateTime->now()->iso8601().'Z';
my $sha = Digest::SHA->new(1);
$sha->add($nonce.$mydate.$password);
my $digest = encode_base64($sha->digest,"");
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Header><Security s:mustUnderstand="1" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><UsernameToken><Username>'.$username.'</Username><Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">'.$digest.'</Password><Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">'.encode_base64($nonce,"").'</Nonce><Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">'.$mydate.'</Created></UsernameToken></Security></s:Header><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken><ImagingSettings><Contrast xmlns="http://www.onvif.org/ver10/schema">'.$CamParams{'Contrast'}.'</Contrast></ImagingSettings><ForcePersistence>true</ForcePersistence></SetImagingSettings></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"';
}
# Decrease Contrast
sub whiteAbsOut
{
Debug( "Iris $CamParams{'Contrast'}" );
my $self = shift;
my $params = shift;
$self->getCamParams() unless($CamParams{'Contrast'});
my $step = $self->getParam( $params, 'step' );
my $min = 0;
$CamParams{'Contrast'} -= $step;
$CamParams{'Contrast'} = $min if ($CamParams{'Contrast'} < $min);
my $cmd = 'onvif/imaging';
my $nonce;
for (0..20){$nonce .= chr(int(rand(254)));}
my $mydate = DateTime->now()->iso8601().'Z';
my $sha = Digest::SHA->new(1);
$sha->add($nonce.$mydate.$password);
my $digest = encode_base64($sha->digest,"");
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"><s:Header><Security s:mustUnderstand="1" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"><UsernameToken><Username>'.$username.'</Username><Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">'.$digest.'</Password><Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">'.encode_base64($nonce,"").'</Nonce><Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">'.$mydate.'</Created></UsernameToken></Security></s:Header><s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><SetImagingSettings xmlns="http://www.onvif.org/ver20/imaging/wsdl"><VideoSourceToken>000</VideoSourceToken><ImagingSettings><Contrast xmlns="http://www.onvif.org/ver10/schema">'.$CamParams{'Contrast'}.'</Contrast></ImagingSettings><ForcePersistence>true</ForcePersistence></SetImagingSettings></s:Body></s:Envelope>';
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/imaging/wsdl/SetImagingSettings"';
}
1;

View File

@ -97,7 +97,8 @@ Event::Event(
db_mutex.lock();
if ( mysql_query( &dbconn, sql ) ) {
Error( "Can't insert event: %s. sql was (%s)", mysql_error( &dbconn ), sql );
exit( mysql_errno( &dbconn ) );
db_mutex.unlock();
return;
}
id = mysql_insert_id( &dbconn );
db_mutex.unlock();
@ -217,6 +218,16 @@ Event::Event(
} // Event::Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap, bool p_videoEvent )
Event::~Event() {
// We cose the videowriter first, because it might take some time, and we don't want to lock the db if we can avoid it
/* Close the video file */
if ( videoStore ) {
delete videoStore;
videoStore = NULL;
}
static char sql[ZM_SQL_MED_BUFSIZ];
struct DeltaTimeval delta_time;
DELTA_TIMEVAL(delta_time, end_time, start_time, DT_PREC_2);
@ -225,25 +236,23 @@ Event::~Event() {
if ( frames > last_db_frame ) {
Debug( 1, "Adding closing frame %d to DB", frames );
snprintf( sql, sizeof(sql),
"insert into Frames ( EventId, FrameId, TimeStamp, Delta ) values ( %d, %d, from_unixtime( %ld ), %s%ld.%02ld )",
"INSERT INTO Frames ( EventId, FrameId, TimeStamp, Delta ) VALUES ( %d, %d, from_unixtime( %ld ), %s%ld.%02ld )",
id, frames, end_time.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec );
db_mutex.lock();
if ( mysql_query( &dbconn, sql ) ) {
Error( "Can't insert frame: %s", mysql_error( &dbconn ) );
} else {
Debug(1,"Success writing last frame");
}
db_mutex.unlock();
}
/* Close the video file */
if ( videoStore ) {
delete videoStore;
videoStore = NULL;
}
snprintf( sql, sizeof(sql), "UPDATE Events SET Name='%s%d', EndTime = from_unixtime( %ld ), Length = %s%ld.%02ld, Frames = %d, AlarmFrames = %d, TotScore = %d, AvgScore = %d, MaxScore = %d, DefaultVideo = '%s' where Id = %d", monitor->EventPrefix(), id, end_time.tv_sec, delta_time.positive?"":"-", delta_time.sec, delta_time.fsec, frames, alarm_frames, tot_score, (int)(alarm_frames?(tot_score/alarm_frames):0), max_score, video_name, id );
db_mutex.lock();
if ( mysql_query( &dbconn, sql ) ) {
Error( "Can't update event: %s", mysql_error( &dbconn ) );
} else {
Debug(1,"Success updating event");
}
db_mutex.unlock();
} // ~Event
@ -380,27 +389,27 @@ void Event::updateNotes( const StringSetMap &newNoteSetMap ) {
#else
static char escapedNotes[ZM_SQL_MED_BUFSIZ];
mysql_real_escape_string( &dbconn, escapedNotes, notes.c_str(), notes.length() );
mysql_real_escape_string(&dbconn, escapedNotes, notes.c_str(), notes.length());
snprintf(sql, sizeof(sql), "UPDATE Events SET Notes = '%s' WHERE Id = %d", escapedNotes, id);
db_mutex.lock();
snprintf( sql, sizeof(sql), "UPDATE Events SET Notes = '%s' WHERE Id = %d", escapedNotes, id );
if ( mysql_query( &dbconn, sql ) ) {
Error( "Can't insert event: %s", mysql_error( &dbconn ) );
if ( mysql_query(&dbconn, sql) ) {
Error("Can't insert event: %s", mysql_error(&dbconn));
}
db_mutex.unlock();
#endif
}
}
void Event::AddFrames( int n_frames, Image **images, struct timeval **timestamps ) {
void Event::AddFrames(int n_frames, Image **images, struct timeval **timestamps) {
for (int i = 0; i < n_frames; i += ZM_SQL_BATCH_SIZE) {
AddFramesInternal(n_frames, i, images, timestamps);
}
}
void Event::AddFramesInternal( int n_frames, int start_frame, Image **images, struct timeval **timestamps ) {
void Event::AddFramesInternal(int n_frames, int start_frame, Image **images, struct timeval **timestamps) {
static char sql[ZM_SQL_LGE_BUFSIZ];
strncpy( sql, "insert into Frames ( EventId, FrameId, TimeStamp, Delta ) values ", sizeof(sql) );
strncpy(sql, "insert into Frames ( EventId, FrameId, TimeStamp, Delta ) values ", sizeof(sql));
int frameCount = 0;
for ( int i = start_frame; i < n_frames && i - start_frame < ZM_SQL_BATCH_SIZE; i++ ) {
if ( timestamps[i]->tv_sec <= 0 ) {

View File

@ -88,43 +88,48 @@ bool EventStream::loadInitialEventData( int monitor_id, time_t event_time ) {
}
bool EventStream::loadInitialEventData( int init_event_id, unsigned int init_frame_id ) {
loadEventData( init_event_id );
loadEventData(init_event_id);
if ( init_frame_id ) {
curr_stream_time = event_data->frames[init_frame_id-1].timestamp;
curr_frame_id = init_frame_id;
if ( init_frame_id >= event_data->frame_count ) {
Error("Invalid frame id specified. %d > %d", init_frame_id, event_data->frame_count );
curr_stream_time = event_data->start_time;
} else {
curr_stream_time = event_data->frames[init_frame_id-1].timestamp;
curr_frame_id = init_frame_id;
}
} else {
curr_stream_time = event_data->start_time;
}
return( true );
return true;
}
bool EventStream::loadEventData( int event_id ) {
bool EventStream::loadEventData(int event_id) {
static char sql[ZM_SQL_MED_BUFSIZ];
snprintf( sql, sizeof(sql), "SELECT MonitorId, StorageId, Frames, unix_timestamp( StartTime ) AS StartTimestamp, (SELECT max(Delta)-min(Delta) FROM Frames WHERE EventId=Events.Id) AS Duration, DefaultVideo, Scheme FROM Events WHERE Id = %d", event_id );
snprintf(sql, sizeof(sql), "SELECT MonitorId, StorageId, Frames, unix_timestamp( StartTime ) AS StartTimestamp, (SELECT max(Delta)-min(Delta) FROM Frames WHERE EventId=Events.Id) AS Duration, DefaultVideo, Scheme FROM Events WHERE Id = %d", event_id);
if ( mysql_query( &dbconn, sql ) ) {
Error( "Can't run query: %s", mysql_error( &dbconn ) );
exit( mysql_errno( &dbconn ) );
if ( mysql_query(&dbconn, sql) ) {
Error("Can't run query: %s", mysql_error(&dbconn));
exit(mysql_errno(&dbconn));
}
MYSQL_RES *result = mysql_store_result( &dbconn );
MYSQL_RES *result = mysql_store_result(&dbconn);
if ( !result ) {
Error( "Can't use query result: %s", mysql_error( &dbconn ) );
exit( mysql_errno( &dbconn ) );
Error("Can't use query result: %s", mysql_error(&dbconn));
exit(mysql_errno(&dbconn));
}
if ( !mysql_num_rows( result ) ) {
Fatal( "Unable to load event %d, not found in DB", event_id );
if ( !mysql_num_rows(result) ) {
Fatal("Unable to load event %d, not found in DB", event_id);
}
MYSQL_ROW dbrow = mysql_fetch_row( result );
MYSQL_ROW dbrow = mysql_fetch_row(result);
if ( mysql_errno( &dbconn ) ) {
Error( "Can't fetch row: %s", mysql_error( &dbconn ) );
exit( mysql_errno( &dbconn ) );
if ( mysql_errno(&dbconn) ) {
Error("Can't fetch row: %s", mysql_error(&dbconn));
exit(mysql_errno(&dbconn));
}
delete event_data;
@ -885,3 +890,17 @@ void EventStream::runStream() {
closeComms();
}
void EventStream::setStreamStart( int init_event_id, unsigned int init_frame_id=0 ) {
loadInitialEventData( init_event_id, init_frame_id );
if ( !(monitor = Monitor::Load( event_data->monitor_id, false, Monitor::QUERY )) ) {
Fatal( "Unable to load monitor id %d for streaming", event_data->monitor_id );
return;
}
}
void EventStream::setStreamStart( int monitor_id, time_t event_time ) {
loadInitialEventData( monitor_id, event_time );
if ( !(monitor = Monitor::Load( event_data->monitor_id, false, Monitor::QUERY )) ) {
Fatal( "Unable to load monitor id %d for streaming", monitor_id );
return;
}
}

View File

@ -110,20 +110,8 @@ class EventStream : public StreamBase {
ffmpeg_input = NULL;
}
void setStreamStart( int init_event_id, unsigned int init_frame_id=0 ) {
loadInitialEventData( init_event_id, init_frame_id );
if ( !(monitor = Monitor::Load( event_data->monitor_id, false, Monitor::QUERY )) ) {
Fatal( "Unable to load monitor id %d for streaming", event_data->monitor_id );
return;
}
}
void setStreamStart( int monitor_id, time_t event_time ) {
loadInitialEventData( monitor_id, event_time );
if ( !(monitor = Monitor::Load( event_data->monitor_id, false, Monitor::QUERY )) ) {
Fatal( "Unable to load monitor id %d for streaming", monitor_id );
return;
}
}
void setStreamStart( int init_event_id, unsigned int init_frame_id );
void setStreamStart( int monitor_id, time_t event_time );
void setStreamMode( StreamMode p_mode ) {
mode = p_mode;
}

View File

@ -131,7 +131,15 @@ int FfmpegCamera::PrimeCapture() {
}
int FfmpegCamera::PreCapture() {
<<<<<<< HEAD
return 0;
=======
// If Reopen was called, then ffmpeg is closed and we need to reopen it.
if ( ! mCanCapture )
return OpenFfmpeg();
// Nothing to do here
return( 0 );
>>>>>>> storageareas
}
int FfmpegCamera::Capture( ZMPacket &zm_packet ) {
@ -346,13 +354,20 @@ int FfmpegCamera::OpenFfmpeg() {
}
} // end if h264
#endif
if ( mVideoCodecContext->codec_id == AV_CODEC_ID_H264 ) {
if ( (mVideoCodec = avcodec_find_decoder_by_name("h264_mmal")) == NULL ) {
Debug(1, "Failed to find decoder (h264_mmal)" );
} else {
Debug(1, "Success finding decoder (h264_mmal)" );
}
}
if ( (!mVideoCodec) and ( (mVideoCodec = avcodec_find_decoder(mVideoCodecContext->codec_id)) == NULL ) ) {
// Try and get the codec from the codec context
Error("Can't find codec for video stream from %s", mPath.c_str());
return -1;
} else {
Debug(1, "Video Found decoder");
Debug(1, "Video Found decoder %s", mVideoCodec->name);
zm_dump_stream_format(mFormatContext, mVideoStreamId, 0, 0);
// Open the codec
@ -468,6 +483,301 @@ int FfmpegCamera::CloseFfmpeg() {
}
return 0;
<<<<<<< HEAD
} // end int FfmpegCamera::CloseFfmpeg()
=======
} // end FfmpegCamera::Close
//Function to handle capture and store
int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event_file ) {
if ( ! mCanCapture ) {
return -1;
}
int ret;
static char errbuf[AV_ERROR_MAX_STRING_SIZE];
int frameComplete = false;
while ( ! frameComplete ) {
av_init_packet( &packet );
ret = av_read_frame( mFormatContext, &packet );
if ( ret < 0 ) {
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
Error( "Unable to read packet from stream %d: error %d \"%s\".", packet.stream_index, ret, errbuf );
return -1;
}
int keyframe = packet.flags & AV_PKT_FLAG_KEY;
dumpPacket(&packet);
//Video recording
if ( recording.tv_sec ) {
uint32_t last_event_id = monitor->GetLastEventId() ;
uint32_t video_writer_event_id = monitor->GetVideoWriterEventId();
if ( last_event_id != video_writer_event_id ) {
Debug(2, "Have change of event. last_event(%d), our current (%d)", last_event_id, video_writer_event_id );
if ( videoStore ) {
Info("Re-starting video storage module");
// I don't know if this is important or not... but I figure we might as well write this last packet out to the store before closing it.
// Also don't know how much it matters for audio.
if ( packet.stream_index == mVideoStreamId ) {
//Write the packet to our video store
int ret = videoStore->writeVideoFramePacket( &packet );
if ( ret < 0 ) { //Less than zero and we skipped a frame
Warning("Error writing last packet to videostore.");
}
} // end if video
delete videoStore;
videoStore = NULL;
have_video_keyframe = false;
monitor->SetVideoWriterEventId( 0 );
} // end if videoStore
} // end if end of recording
if ( last_event_id and ! videoStore ) {
//Instantiate the video storage module
if ( record_audio ) {
if ( mAudioStreamId == -1 ) {
Debug(3, "Record Audio on but no audio stream found");
videoStore = new VideoStore((const char *) event_file, "mp4",
mFormatContext->streams[mVideoStreamId],
NULL,
startTime,
this->getMonitor());
} else {
Debug(3, "Video module initiated with audio stream");
videoStore = new VideoStore((const char *) event_file, "mp4",
mFormatContext->streams[mVideoStreamId],
mFormatContext->streams[mAudioStreamId],
startTime,
this->getMonitor());
}
} else {
if ( mAudioStreamId >= 0 ) {
Debug(3, "Record_audio is false so exclude audio stream");
}
videoStore = new VideoStore((const char *) event_file, "mp4",
mFormatContext->streams[mVideoStreamId],
NULL,
startTime,
this->getMonitor());
} // end if record_audio
if ( ! videoStore->open() ) {
delete videoStore;
videoStore = NULL;
} else {
monitor->SetVideoWriterEventId( last_event_id );
// Need to write out all the frames from the last keyframe?
// No... need to write out all frames from when the event began. Due to PreEventFrames, this could be more than since the last keyframe.
unsigned int packet_count = 0;
ZMPacket *queued_packet;
// Clear all packets that predate the moment when the recording began
packetqueue.clear_unwanted_packets( &recording, mVideoStreamId );
while ( ( queued_packet = packetqueue.popPacket() ) ) {
AVPacket *avp = queued_packet->av_packet();
packet_count += 1;
//Write the packet to our video store
Debug(2, "Writing queued packet stream: %d KEY %d, remaining (%d)", avp->stream_index, avp->flags & AV_PKT_FLAG_KEY, packetqueue.size() );
if ( avp->stream_index == mVideoStreamId ) {
ret = videoStore->writeVideoFramePacket( avp );
have_video_keyframe = true;
} else if ( avp->stream_index == mAudioStreamId ) {
ret = videoStore->writeAudioFramePacket( avp );
} else {
Warning("Unknown stream id in queued packet (%d)", avp->stream_index );
ret = -1;
}
if ( ret < 0 ) {
//Less than zero and we skipped a frame
}
delete queued_packet;
} // end while packets in the packetqueue
Debug(2, "Wrote %d queued packets", packet_count );
}
} // end if ! was recording
} else {
// Not recording
if ( videoStore ) {
Info("Deleting videoStore instance");
delete videoStore;
videoStore = NULL;
have_video_keyframe = false;
monitor->SetVideoWriterEventId( 0 );
}
// Buffer video packets, since we are not recording.
// All audio packets are keyframes, so only if it's a video keyframe
if ( packet.stream_index == mVideoStreamId ) {
if ( keyframe ) {
Debug(3, "Clearing queue");
packetqueue.clearQueue( monitor->GetPreEventCount(), mVideoStreamId );
packetqueue.queuePacket( &packet );
} else if ( packetqueue.size() ) {
// it's a keyframe or we already have something in the queue
packetqueue.queuePacket( &packet );
}
} else if ( packet.stream_index == mAudioStreamId ) {
// The following lines should ensure that the queue always begins with a video keyframe
//Debug(2, "Have audio packet, reocrd_audio is (%d) and packetqueue.size is (%d)", record_audio, packetqueue.size() );
if ( record_audio && packetqueue.size() ) {
// if it's audio, and we are doing audio, and there is already something in the queue
packetqueue.queuePacket( &packet );
}
}
} // end if recording or not
if ( packet.stream_index == mVideoStreamId ) {
// only do decode if we have had a keyframe, should save a few cycles.
if ( have_video_keyframe || keyframe ) {
if ( videoStore ) {
//Write the packet to our video store
int ret = videoStore->writeVideoFramePacket( &packet );
if ( ret < 0 ) { //Less than zero and we skipped a frame
zm_av_packet_unref( &packet );
return 0;
}
have_video_keyframe = true;
}
} // end if keyframe or have_video_keyframe
Debug(4, "about to decode video" );
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
ret = avcodec_send_packet( mVideoCodecContext, &packet );
if ( ret < 0 ) {
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
Error( "Unable to send packet at frame %d: %s, continuing", frameCount, errbuf );
zm_av_packet_unref( &packet );
continue;
}
#if HAVE_AVUTIL_HWCONTEXT_H
if ( hwaccel ) {
ret = avcodec_receive_frame( mVideoCodecContext, hwFrame );
if ( ret < 0 ) {
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
Error( "Unable to send packet at frame %d: %s, continuing", frameCount, errbuf );
zm_av_packet_unref( &packet );
continue;
}
ret = av_hwframe_transfer_data(mRawFrame, hwFrame, 0);
if (ret < 0) {
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
Error( "Unable to transfer frame at frame %d: %s, continuing", frameCount, errbuf );
zm_av_packet_unref( &packet );
continue;
}
} else {
#endif
ret = avcodec_receive_frame( mVideoCodecContext, mRawFrame );
if ( ret < 0 ) {
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
Warning( "Unable to receive frame %d: %s, continuing", frameCount, errbuf );
zm_av_packet_unref( &packet );
continue;
}
#if HAVE_AVUTIL_HWCONTEXT_H
}
#endif
frameComplete = 1;
# else
ret = zm_avcodec_decode_video( mVideoCodecContext, mRawFrame, &frameComplete, &packet );
if ( ret < 0 ) {
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
Error( "Unable to decode frame at frame %d: %s, continuing", frameCount, errbuf );
zm_av_packet_unref( &packet );
continue;
}
#endif
if ( frameComplete ) {
Debug( 4, "Got frame %d", frameCount );
uint8_t* directbuffer;
/* Request a writeable buffer of the target image */
directbuffer = image.WriteBuffer(width, height, colours, subpixelorder);
if ( directbuffer == NULL ) {
Error("Failed requesting writeable buffer for the captured image.");
zm_av_packet_unref( &packet );
return (-1);
}
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
av_image_fill_arrays(mFrame->data, mFrame->linesize, directbuffer, imagePixFormat, width, height, 1);
#else
avpicture_fill( (AVPicture *)mFrame, directbuffer, imagePixFormat, width, height);
#endif
if (sws_scale(mConvertContext, mRawFrame->data, mRawFrame->linesize,
0, mVideoCodecContext->height, mFrame->data, mFrame->linesize) < 0) {
Error("Unable to convert raw format %u to target format %u at frame %d",
mVideoCodecContext->pix_fmt, imagePixFormat, frameCount);
return -1;
}
frameCount++;
} else {
Debug( 3, "Not framecomplete after av_read_frame" );
} // end if frameComplete
} else if ( packet.stream_index == mAudioStreamId ) { //FIXME best way to copy all other streams
frameComplete = 1;
if ( videoStore ) {
if ( record_audio ) {
if ( have_video_keyframe ) {
Debug(3, "Recording audio packet streamindex(%d) packetstreamindex(%d)", mAudioStreamId, packet.stream_index );
//Write the packet to our video store
//FIXME no relevance of last key frame
int ret = videoStore->writeAudioFramePacket( &packet );
if ( ret < 0 ) {//Less than zero and we skipped a frame
Warning("Failure to write audio packet.");
zm_av_packet_unref( &packet );
return 0;
}
} else {
Debug(3, "Not recording audio yet because we don't have a video keyframe yet");
}
} else {
Debug(4, "Not doing recording of audio packet" );
}
} else {
Debug(4, "Have audio packet, but not recording atm" );
}
zm_av_packet_unref( &packet );
return 0;
} else {
#if LIBAVUTIL_VERSION_CHECK(56, 23, 0, 23, 0)
Debug( 3, "Some other stream index %d, %s", packet.stream_index, av_get_media_type_string( mFormatContext->streams[packet.stream_index]->codecpar->codec_type) );
#else
Debug( 3, "Some other stream index %d", packet.stream_index );
#endif
} // end if is video or audio or something else
// the packet contents are ref counted... when queuing, we allocate another packet and reference it with that one, so we should always need to unref here, which should not affect the queued version.
zm_av_packet_unref( &packet );
} // end while ! frameComplete
return frameCount;
} // end FfmpegCamera::CaptureAndRecord
>>>>>>> storageareas
#endif // HAVE_LIBAVFORMAT

View File

@ -143,13 +143,69 @@ AVFrame *FFmpeg_Input::get_frame( int stream_id, int frame_number ) {
return NULL;
}
<<<<<<< HEAD
if ( ( stream_id < 0 ) || ( packet.stream_index != stream_id ) ) {
Warning("Packet is not for our stream (%d)", packet.stream_index);
return NULL;
=======
if ( (stream_id < 0 ) || ( packet.stream_index == stream_id ) ) {
Debug(3,"Packet is for our stream (%d)", packet.stream_index );
AVCodecContext *context = streams[packet.stream_index].context;
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
ret = avcodec_send_packet( context, &packet );
if ( ret < 0 ) {
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
Error( "Unable to send packet at frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf );
zm_av_packet_unref( &packet );
continue;
} else {
Debug(1, "Success getting a packet");
}
#if HAVE_AVUTIL_HWCONTEXT_H
if ( hwaccel ) {
ret = avcodec_receive_frame( context, hwFrame );
if ( ret < 0 ) {
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
Error( "Unable to receive frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf );
zm_av_packet_unref( &packet );
continue;
}
ret = av_hwframe_transfer_data(frame, hwFrame, 0);
if (ret < 0) {
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
Error( "Unable to transfer frame at frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf );
zm_av_packet_unref( &packet );
continue;
}
} else {
#endif
Debug(1,"Getting a frame?");
ret = avcodec_receive_frame( context, frame );
if ( ret < 0 ) {
av_strerror( ret, errbuf, AV_ERROR_MAX_STRING_SIZE );
Error( "Unable to send packet at frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf );
zm_av_packet_unref( &packet );
continue;
}
#if HAVE_AVUTIL_HWCONTEXT_H
>>>>>>> storageareas
}
<<<<<<< HEAD
if ( ! zm_receive_frame( streams[packet.stream_index].context, frame, packet ) ) {
Error("Unable to get frame %d, continuing", streams[packet.stream_index].frame_count);
=======
frameComplete = 1;
# else
ret = zm_avcodec_decode_video(context, frame, &frameComplete, &packet);
if ( ret < 0 ) {
av_strerror(ret, errbuf, AV_ERROR_MAX_STRING_SIZE);
Error( "Unable to decode frame at frame %d: %s, continuing", streams[packet.stream_index].frame_count, errbuf );
>>>>>>> storageareas
zm_av_packet_unref( &packet );
continue;
} else {

View File

@ -326,7 +326,8 @@ Monitor::Monitor(
zones( p_zones ),
timestamps( 0 ),
images( 0 ),
privacy_bitmask( NULL )
privacy_bitmask( NULL ),
event_delete_thread(NULL)
{
strncpy( name, p_name, sizeof(name)-1 );
@ -477,7 +478,6 @@ Monitor::Monitor(
adaptive_skip = true;
ReloadLinkedMonitors( p_linked_monitors );
videoStore = NULL;
} // Monitor::Monitor
bool Monitor::connect() {
@ -578,12 +578,13 @@ bool Monitor::connect() {
} // Monitor::connect
Monitor::~Monitor() {
if ( videoStore ) {
delete videoStore;
videoStore = NULL;
if ( event_delete_thread ) {
event_delete_thread->join();
delete event_delete_thread;
event_delete_thread = NULL;
}
delete packetqueue;
packetqueue = NULL;
if ( timestamps ) {
delete[] timestamps;
@ -593,13 +594,18 @@ Monitor::~Monitor() {
delete[] images;
images = 0;
}
delete packetqueue;
packetqueue = NULL;
if ( privacy_bitmask ) {
delete[] privacy_bitmask;
privacy_bitmask = NULL;
}
if ( mem_ptr ) {
if ( event ) {
Info( "%s: image_count:%d - Closing event %d, shutting down", name, image_count, event->Id() );
Info("%s: image_count:%d - Closing event %d, shutting down", name, image_count, event->Id() );
closeEvent();
}
@ -607,7 +613,7 @@ Monitor::~Monitor() {
delete next_buffer.image;
}
#if 1
for ( int i = 0; i < image_buffer_count; i++ ) {
for ( int i=0; i < image_buffer_count; i++ ) {
delete image_buffer[i].image;
}
#endif
@ -620,15 +626,15 @@ Monitor::~Monitor() {
shared_data->last_read_time = 0;
} else if ( purpose == CAPTURE ) {
shared_data->valid = false;
memset( mem_ptr, 0, mem_size );
memset(mem_ptr, 0, mem_size);
}
#if ZM_MEM_MAPPED
if ( msync( mem_ptr, mem_size, MS_SYNC ) < 0 )
Error( "Can't msync: %s", strerror(errno) );
if ( munmap( mem_ptr, mem_size ) < 0 )
Fatal( "Can't munmap: %s", strerror(errno) );
close( map_fd );
if ( msync(mem_ptr, mem_size, MS_SYNC) < 0 )
Error("Can't msync: %s", strerror(errno));
if ( munmap(mem_ptr, mem_size) < 0 )
Fatal("Can't munmap: %s", strerror(errno));
close(map_fd);
if ( purpose == CAPTURE ) {
// How about we store this in the object on instantiation so that we don't have to do this again.
@ -641,20 +647,20 @@ Monitor::~Monitor() {
}
#else // ZM_MEM_MAPPED
struct shmid_ds shm_data;
if ( shmctl( shm_id, IPC_STAT, &shm_data ) < 0 ) {
Error( "Can't shmctl: %s", strerror(errno) );
exit( -1 );
if ( shmctl(shm_id, IPC_STAT, &shm_data) < 0 ) {
Error("Can't shmctl: %s", strerror(errno));
exit(-1);
}
if ( shm_data.shm_nattch <= 1 ) {
if ( shmctl( shm_id, IPC_RMID, 0 ) < 0 ) {
Error( "Can't shmctl: %s", strerror(errno) );
exit( -1 );
if ( shmctl(shm_id, IPC_RMID, 0) < 0 ) {
Error("Can't shmctl: %s", strerror(errno));
exit(-1);
}
}
#endif // ZM_MEM_MAPPED
} // end if mem_ptr
for ( int i = 0; i < n_zones; i++ ) {
for ( int i=0; i < n_zones; i++ ) {
delete zones[i];
}
delete[] zones;
@ -663,29 +669,29 @@ Monitor::~Monitor() {
delete storage;
}
void Monitor::AddZones( int p_n_zones, Zone *p_zones[] ) {
for ( int i = 0; i < n_zones; i++ )
void Monitor::AddZones(int p_n_zones, Zone *p_zones[]) {
for ( int i=0; i < n_zones; i++ )
delete zones[i];
delete[] zones;
n_zones = p_n_zones;
zones = p_zones;
}
void Monitor::AddPrivacyBitmask( Zone *p_zones[] ) {
void Monitor::AddPrivacyBitmask(Zone *p_zones[]) {
if ( privacy_bitmask ) {
delete[] privacy_bitmask;
privacy_bitmask = NULL;
}
Image *privacy_image = NULL;
for ( int i = 0; i < n_zones; i++ ) {
for ( int i=0; i < n_zones; i++ ) {
if ( p_zones[i]->IsPrivacy() ) {
if ( !privacy_image ) {
privacy_image = new Image( width, height, 1, ZM_SUBPIX_ORDER_NONE);
privacy_image = new Image(width, height, 1, ZM_SUBPIX_ORDER_NONE);
privacy_image->Clear();
}
privacy_image->Fill( 0xff, p_zones[i]->GetPolygon() );
privacy_image->Outline( 0xff, p_zones[i]->GetPolygon() );
privacy_image->Fill(0xff, p_zones[i]->GetPolygon());
privacy_image->Outline(0xff, p_zones[i]->GetPolygon());
}
} // end foreach zone
if ( privacy_image )
@ -693,7 +699,7 @@ void Monitor::AddPrivacyBitmask( Zone *p_zones[] ) {
}
Monitor::State Monitor::GetState() const {
return( (State)shared_data->state );
return (State)shared_data->state;
}
int Monitor::GetImage( int index, int scale ) {
@ -1571,50 +1577,50 @@ bool Monitor::Analyse() {
} // end Monitor::Analyze
void Monitor::Reload() {
Debug( 1, "Reloading monitor %s", name );
Debug(1, "Reloading monitor %s", name);
if ( event )
Info( "%s: %03d - Closing event %d, reloading", name, image_count, event->Id() );
Info("%s: %03d - Closing event %d, reloading", name, image_count, event->Id());
closeEvent();
static char sql[ZM_SQL_MED_BUFSIZ];
// This seems to have fallen out of date.
snprintf( sql, sizeof(sql), "select Function+0, Enabled, LinkedMonitors, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, WarmupCount, PreEventCount, PostEventCount, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPSLimit, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour from Monitors where Id = '%d'", id );
snprintf(sql, sizeof(sql), "select Function+0, Enabled, LinkedMonitors, EventPrefix, LabelFormat, LabelX, LabelY, LabelSize, WarmupCount, PreEventCount, PostEventCount, AlarmFrameCount, SectionLength, FrameSkip, MotionFrameSkip, AnalysisFPSLimit, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS, FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, SignalCheckColour from Monitors where Id = '%d'", id);
db_mutex.lock();
if ( mysql_query( &dbconn, sql ) ) {
Error( "Can't run query: %s", mysql_error( &dbconn ) );
if ( mysql_query(&dbconn, sql) ) {
Error("Can't run query: %s", mysql_error(&dbconn));
db_mutex.unlock();
exit( mysql_errno( &dbconn ) );
}
MYSQL_RES *result = mysql_store_result( &dbconn );
db_mutex.unlock();
if ( !result ) {
Error( "Can't use query result: %s", mysql_error( &dbconn ) );
exit( mysql_errno( &dbconn ) );
}
int n_monitors = mysql_num_rows( result );
if ( n_monitors != 1 ) {
Error( "Bogus number of monitors, %d, returned. Can't reload", n_monitors );
return;
}
if ( MYSQL_ROW dbrow = mysql_fetch_row( result ) ) {
MYSQL_RES *result = mysql_store_result(&dbconn);
db_mutex.unlock();
if ( !result ) {
Error("Can't use query result: %s. Can't reload", mysql_error(&dbconn));
return;
}
int n_monitors = mysql_num_rows(result);
if ( n_monitors != 1 ) {
Error("Bogus number of monitors, %d, returned. Can't reload", n_monitors);
return;
}
if ( MYSQL_ROW dbrow = mysql_fetch_row(result) ) {
int index = 0;
function = (Function)atoi(dbrow[index++]);
enabled = atoi(dbrow[index++]);
const char *p_linked_monitors = dbrow[index++];
if ( dbrow[index] ) {
strncpy( event_prefix, dbrow[index++], sizeof(event_prefix)-1 );
strncpy(event_prefix, dbrow[index++], sizeof(event_prefix)-1);
} else {
event_prefix[0] = 0;
index++;
}
if ( dbrow[index] ) {
strncpy( label_format, dbrow[index++], sizeof(label_format)-1 );
strncpy(label_format, dbrow[index++], sizeof(label_format)-1);
} else {
label_format[0] = 0;
index++;
@ -1638,7 +1644,6 @@ void Monitor::Reload() {
alarm_ref_blend_perc = atoi(dbrow[index++]);
track_motion = atoi(dbrow[index++]);
if ( dbrow[index][0] == '#' )
signal_check_colour = strtol(dbrow[index]+1,0,16);
else
@ -1651,32 +1656,31 @@ void Monitor::Reload() {
shared_data->active = true;
ready_count = image_count+warmup_count;
ReloadLinkedMonitors( p_linked_monitors );
ReloadLinkedMonitors(p_linked_monitors);
}
if ( mysql_errno( &dbconn ) ) {
Error( "Can't fetch row: %s", mysql_error( &dbconn ) );
exit( mysql_errno( &dbconn ) );
if ( mysql_errno(&dbconn) ) {
Error("Can't fetch row: %s", mysql_error(&dbconn));
}
mysql_free_result( result );
mysql_free_result(result);
ReloadZones();
}
} // end void Monitor::Reload()
void Monitor::ReloadZones() {
Debug( 1, "Reloading zones for monitor %s", name );
for( int i = 0; i < n_zones; i++ ) {
Debug(1, "Reloading zones for monitor %s", name);
for( int i=0; i < n_zones; i++ ) {
delete zones[i];
}
delete[] zones;
zones = 0;
n_zones = Zone::Load( this, zones );
n_zones = Zone::Load(this, zones);
//DumpZoneImage();
}
} // end void Monitor::ReloadZones()
void Monitor::ReloadLinkedMonitors( const char *p_linked_monitors ) {
Debug( 1, "Reloading linked monitors for monitor %s, '%s'", name, p_linked_monitors );
void Monitor::ReloadLinkedMonitors(const char *p_linked_monitors) {
Debug(1, "Reloading linked monitors for monitor %s, '%s'", name, p_linked_monitors);
if ( n_linked_monitors ) {
for( int i = 0; i < n_linked_monitors; i++ ) {
for( int i=0; i < n_linked_monitors; i++ ) {
delete linked_monitors[i];
}
delete[] linked_monitors;
@ -1688,6 +1692,7 @@ void Monitor::ReloadLinkedMonitors( const char *p_linked_monitors ) {
int n_link_ids = 0;
unsigned int link_ids[256];
// This nasty code picks out strings of digits from p_linked_monitors and tries to load them.
char link_id_str[8];
char *dest_ptr = link_id_str;
const char *src_ptr = p_linked_monitors;
@ -1729,35 +1734,35 @@ void Monitor::ReloadLinkedMonitors( const char *p_linked_monitors ) {
linked_monitors = new MonitorLink *[n_link_ids];
int count = 0;
for ( int i = 0; i < n_link_ids; i++ ) {
Debug( 1, "Checking linked monitor %d", link_ids[i] );
Debug(1, "Checking linked monitor %d", link_ids[i]);
static char sql[ZM_SQL_SML_BUFSIZ];
snprintf( sql, sizeof(sql), "select Id, Name from Monitors where Id = %d and Function != 'None' and Function != 'Monitor' and Enabled = 1", link_ids[i] );
snprintf(sql, sizeof(sql), "SELECT Id, Name FROM Monitors WHERE Id = %d AND Function != 'None' AND Function != 'Monitor' AND Enabled = 1", link_ids[i] );
db_mutex.lock();
if ( mysql_query( &dbconn, sql ) ) {
Error( "Can't run query: %s", mysql_error( &dbconn ) );
if ( mysql_query(&dbconn, sql) ) {
Error("Can't run query: %s", mysql_error(&dbconn));
db_mutex.unlock();
exit( mysql_errno( &dbconn ) );
continue;
}
MYSQL_RES *result = mysql_store_result( &dbconn );
MYSQL_RES *result = mysql_store_result(&dbconn);
db_mutex.unlock();
if ( !result ) {
Error( "Can't use query result: %s", mysql_error( &dbconn ) );
exit( mysql_errno( &dbconn ) );
Error("Can't use query result: %s", mysql_error(&dbconn));
continue;
}
int n_monitors = mysql_num_rows( result );
int n_monitors = mysql_num_rows(result);
if ( n_monitors == 1 ) {
MYSQL_ROW dbrow = mysql_fetch_row( result );
Debug( 1, "Linking to monitor %d", link_ids[i] );
linked_monitors[count++] = new MonitorLink( link_ids[i], dbrow[1] );
MYSQL_ROW dbrow = mysql_fetch_row(result);
Debug(1, "Linking to monitor %d", link_ids[i]);
linked_monitors[count++] = new MonitorLink(link_ids[i], dbrow[1]);
} else {
Warning( "Can't link to monitor %d, invalid id, function or not enabled", link_ids[i] );
Warning("Can't link to monitor %d, invalid id, function or not enabled", link_ids[i]);
}
mysql_free_result( result );
}
mysql_free_result(result);
} // end foreach n_link_id
n_linked_monitors = count;
}
} // end if n_link_ids > 0
}
}
@ -3048,12 +3053,22 @@ bool Monitor::closeEvent() {
if ( function == RECORD || function == MOCORD ) {
gettimeofday( &(event->EndTime()), NULL );
}
delete event;
if ( event_delete_thread ) {
event_delete_thread->join();
delete event_delete_thread;
event_delete_thread = NULL;
}
event_delete_thread = new std::thread([](Event *event) {
Event * e = event;
event = NULL;
delete e;
e = NULL;
}, event );
video_store_data->recording = (struct timeval){0};
event = 0;
return( true );
event = NULL;
return true;
}
return( false );
return false;
}
unsigned int Monitor::DetectMotion( const Image &comp_image, Event::StringSet &zoneSet ) {

View File

@ -22,6 +22,7 @@
#include <vector>
#include <sstream>
#include <thread>
#include "zm.h"
#include "zm_coord.h"
@ -336,6 +337,7 @@ protected:
Image **images;
const unsigned char *privacy_bitmask;
std::thread *event_delete_thread; // Used to close events, but continue processing.
int n_linked_monitors;
MonitorLink **linked_monitors;

View File

@ -88,12 +88,11 @@ bool StreamBase::checkCommandQueue() {
//Error( "Partial message received, expected %d bytes, got %d", sizeof(msg), nbytes );
//}
else {
Debug(2, "Message length is (%d)", nbytes );
processCommand( &msg );
return( true );
}
} else {
Error("sd is < 0");
Warning("No sd in checkCommandQueue, comms not open?");
}
return( false );
}

View File

@ -211,6 +211,7 @@ int X264MP4Writer::Open() {
int X264MP4Writer::Close() {
/* Flush all pending frames */
for ( int i = (x264_encoder_delayed_frames(x264enc) + 1); i > 0; i-- ) {
Debug(1,"Encoding delayed frame");
x264encodeloop(true);
}
@ -220,6 +221,7 @@ int X264MP4Writer::Close() {
/* Close MP4 handle */
MP4Close(mp4h);
Debug(1,"Optimising");
/* Required for proper HTTP streaming */
MP4Optimize((path + ".incomplete").c_str(), path.c_str());
@ -228,7 +230,7 @@ int X264MP4Writer::Close() {
bOpen = false;
Debug(7, "Video closed. Total frames: %d", frame_count);
Debug(1, "Video closed. Total frames: %d", frame_count);
return 0;
}
@ -413,7 +415,7 @@ void X264MP4Writer::x264encodeloop(bool bFlush) {
}
if ( frame_size > 0 || bFlush ) {
Debug(8, "x264 Frame: %d PTS: %d DTS: %d Size: %d\n",
Debug(1, "x264 Frame: %d PTS: %d DTS: %d Size: %d\n",
frame_count, x264picout.i_pts, x264picout.i_dts, frame_size);
/* Handle the previous frame */
@ -490,7 +492,7 @@ void X264MP4Writer::x264encodeloop(bool bFlush) {
}
} else if ( frame_size == 0 ) {
Debug(7, "x264 encode returned zero. Delayed frames: %d",
Debug(1, "x264 encode returned zero. Delayed frames: %d",
x264_encoder_delayed_frames(x264enc));
} else {
Error("x264 encode failed: %d", frame_size);

View File

@ -283,10 +283,10 @@ int main( int argc, const char *argv[] ) {
stream.setStreamMode( replay );
stream.setStreamQueue( connkey );
if ( monitor_id && event_time ) {
stream.setStreamStart( monitor_id, event_time );
stream.setStreamStart(monitor_id, event_time);
} else {
Debug(3, "Setting stream start to frame (%d)", frame_id);
stream.setStreamStart( event_id, frame_id );
stream.setStreamStart(event_id, frame_id);
}
if ( mode == ZMS_JPEG ) {
stream.setStreamType( EventStream::STREAM_JPEG );

View File

@ -295,7 +295,7 @@ if [ "${TRAVIS_EVENT_TYPE}" == "cron" ] || [ "${TRAVIS}" != "true" ]; then
execpackpack
# Steps common to Debian based distros
elif [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ]; then
elif [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ] || [ "${OS}" == "raspbian" ]; then
echo "Begin ${OS} ${DIST} build..."
setdebpkgname

230
web/includes/Control.php Normal file
View File

@ -0,0 +1,230 @@
<?php
require_once( 'database.php' );
require_once( 'Server.php' );
class Control {
private $defaults = array(
'CanMove' => 0,
'CanMoveDiag' => 0,
'CanMoveMap' => 0,
'CanMoveAbs' => 0,
'CanMoveRel' => 0,
'CanMoveCon' => 0,
'CanPan' => 0,
'MinPanRange' => NULL,
'MaxPanRange' => NULL,
'MinPanStep' => NULL,
'MaxPanStep' => NULL,
'HasPanSpeed' => 0,
'MinPanSpeed' => NULL,
'MaxPanSpeed' => NULL,
'HasTurboPan' => 0,
'TurboPanSpeed' => NULL,
'CanTilt' => 0,
'MinTiltRange' => NULL,
'MaxTiltRange' => NULL,
'MinTiltStep' => NULL,
'MaxTiltStep' => NULL,
'HasTiltSpeed' => 0,
'MinTiltSpeed' => NULL,
'MaxTiltSpeed' => NULL,
'HasTurboTilt' => 0,
'TurboTiltSpeed' => NULL,
'CanZoom' => 0,
'CanZoomAbs' => 0,
'CanZoomRel' => 0,
'CanZoomCon' => 0,
'MinZoomRange' => NULL,
'MaxZoomRange' => NULL,
'MinZoomStep' => NULL,
'MaxZoomStep' => NULL,
'HasZoomSpeed' => 0,
'MinZoomSpeed' => NULL,
'MaxZoomSpeed' => NULL,
'CanFocus' => 0,
'CanAutoFocus' => 0,
'CanFocusAbs' => 0,
'CanFocusRel' => 0,
'CanFocusCon' => 0,
'MinFocusRange' => NULL,
'MaxFocusRange' => NULL,
'MinFocusStep' => NULL,
'MaxFocusStep' => NULL,
'HasFocusSpeed' => 0,
'MinFocusSpeed' => NULL,
'MaxFocusSpeed' => NULL,
'CanIris' => 0,
'CanAutoIris' => 0,
'CanIrisAbs' => 0,
'CanIrisRel' => 0,
'CanIrisCon' => 0,
'MinIrisRange' => NULL,
'MaxIrisRange' => NULL,
'MinIrisStep' => NULL,
'MaxIrisStep' => NULL,
'HasIrisSpeed' => 0,
'MinIrisSpeed' => NULL,
'MaxIrisSpeed' => NULL,
'CanGain' => 0,
'CanAutoGain' => 0,
'CanGainAbs' => 0,
'CanGainRel' => 0,
'CanGainCon' => 0,
'MinGainRange' => NULL,
'MaxGainRange' => NULL,
'MinGainStep' => NULL,
'MaxGainStep' => NULL,
'HasGainSpeed' => 0,
'MinGainSpeed' => NULL,
'MaxGainSpeed' => NULL,
'CanWhite' => 0,
'CanAutoWhite' => 0,
'CanWhiteAbs' => 0,
'CanWhiteRel' => 0,
'CanWhiteCon' => 0,
'MinWhiteRange' => NULL,
'MaxWhiteRange' => NULL,
'MinWhiteStep' => NULL,
'MaxWhiteStep' => NULL,
'HasWhiteSpeed' => 0,
'MinWhiteSpeed' => NULL,
'MaxWhiteSpeed' => NULL,
'HasPresets' => 0,
'NumPresets' => 0,
'HasHomePreset' => 0,
'CanSetPresets' => 0,
'Name' => 'New',
'Type' => 'Local',
'Protocol' => NULL
);
public function __construct( $IdOrRow = NULL ) {
if ( $IdOrRow ) {
$row = NULL;
if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) {
$row = dbFetchOne( 'SELECT * FROM Control WHERE Id=?', NULL, array( $IdOrRow ) );
if ( ! $row ) {
Error("Unable to load Control record for Id=" . $IdOrRow );
}
} elseif ( is_array( $IdOrRow ) ) {
$row = $IdOrRow;
} else {
Error("Unknown argument passed to Control Constructor ($IdOrRow)");
return;
}
if ( $row ) {
foreach ($row as $k => $v) {
$this->{$k} = $v;
}
} else {
Error('No row for Control ' . $IdOrRow );
}
} # end if isset($IdOrRow)
} // end function __construct
public function __call($fn, array $args){
if ( count($args) ) {
$this->{$fn} = $args[0];
}
if ( array_key_exists($fn, $this) ) {
return $this->{$fn};
#array_unshift($args, $this);
#call_user_func_array( $this->{$fn}, $args);
} else {
if ( array_key_exists($fn, $this->control_fields) ) {
return $this->control_fields{$fn};
} else if ( array_key_exists( $fn, $this->defaults ) ) {
return $this->defaults{$fn};
} else {
$backTrace = debug_backtrace();
$file = $backTrace[1]['file'];
$line = $backTrace[1]['line'];
Warning( "Unknown function call Control->$fn from $file:$line" );
}
}
}
public function set( $data ) {
foreach ($data as $k => $v) {
if ( is_array( $v ) ) {
# perhaps should turn into a comma-separated string
$this->{$k} = implode(',',$v);
} else if ( is_string( $v ) ) {
$this->{$k} = trim( $v );
} else if ( is_integer( $v ) ) {
$this->{$k} = $v;
} else if ( is_bool( $v ) ) {
$this->{$k} = $v;
} else {
Error( "Unknown type $k => $v of var " . gettype( $v ) );
$this->{$k} = $v;
}
}
}
public static function find_all( $parameters = null, $options = null ) {
$filters = array();
$sql = 'SELECT * FROM Controls ';
$values = array();
if ( $parameters ) {
$fields = array();
$sql .= 'WHERE ';
foreach ( $parameters as $field => $value ) {
if ( $value == null ) {
$fields[] = $field.' IS NULL';
} else if ( is_array( $value ) ) {
$func = function(){return '?';};
$fields[] = $field.' IN ('.implode(',', array_map( $func, $value ) ). ')';
$values += $value;
} else {
$fields[] = $field.'=?';
$values[] = $value;
}
}
$sql .= implode(' AND ', $fields );
}
if ( $options and isset($options['order']) ) {
$sql .= ' ORDER BY ' . $options['order'];
}
$result = dbQuery($sql, $values);
$results = $result->fetchALL(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Control');
foreach ( $results as $row => $obj ) {
$filters[] = $obj;
}
return $filters;
}
public function save( $new_values = null ) {
if ( $new_values ) {
foreach ( $new_values as $k=>$v ) {
$this->{$k} = $v;
}
}
// Set default values
foreach ( $this->defaults as $k=>$v ) {
if ( ( ! array_key_exists( $k, $this ) ) or ( $this->{$k} == '' ) ) {
$this->{$k} = $v;
}
}
$fields = array_keys( $this->defaults );
if ( array_key_exists( 'Id', $this ) ) {
$sql = 'UPDATE Controls SET '.implode(', ', array_map( function($field) {return $field.'=?';}, $fields ) ) . ' WHERE Id=?';
$values = array_map( function($field){return $this->{$field};}, $fields );
$values[] = $this->{'Id'};
dbQuery( $sql, $values );
} else {
$sql = 'INSERT INTO Controls SET '.implode(', ', array_map( function($field) {return $field.'=?';}, $fields ) ) . '';
$values = array_map( function($field){return $this->{$field};}, $fields );
dbQuery( $sql, $values );
$this->{'Id'} = dbInsertId();
}
} // end function save
} // end class Control
?>

View File

@ -168,15 +168,22 @@ class Event {
} else {
$streamSrc .= $_SERVER['HTTP_HOST'];
}
$streamSrc .= ( ZM_BASE_PATH != '/' ? ZM_BASE_PATH : '' ).'/index.php?view=view_video&eid='.$this->{'Id'};
return $streamSrc;
$streamSrc .= ( ZM_BASE_PATH != '/' ? ZM_BASE_PATH : '' ).'/index.php';
$args['eid'] = $this->{'Id'};
$args['view'] = 'view_video';
} else {
$streamSrc = ZM_BASE_URL.ZM_PATH_ZMS;
$args['source'] = 'event';
$args['event'] = $this->{'Id'};
if ( ( (!isset($args['mode'])) or ( $args['mode'] != 'single' ) ) && !empty($GLOBALS['connkey']) ) {
$args['connkey'] = $GLOBALS['connkey'];
}
if ( ZM_RAND_STREAM ) {
$args['rand'] = time();
}
}
$streamSrc = ZM_BASE_URL.ZM_PATH_ZMS;
$args['source'] = 'event';
$args['event'] = $this->{'Id'};
if ( ZM_OPT_USE_AUTH ) {
if ( ZM_AUTH_RELAY == 'hashed' ) {
$args['auth'] = generateAuthHash( ZM_AUTH_HASH_IPS );
@ -187,16 +194,10 @@ class Event {
$args['user'] = $_SESSION['username'];
}
}
if ( ( (!isset($args['mode'])) or ( $args['mode'] != 'single' ) ) && !empty($GLOBALS['connkey']) ) {
$args['connkey'] = $GLOBALS['connkey'];
}
if ( ZM_RAND_STREAM ) {
$args['rand'] = time();
}
$streamSrc .= '?'.http_build_query( $args,'', $querySep );
return( $streamSrc );
return $streamSrc;
} // end function getStreamSrc
function DiskSpace( $new='' ) {

View File

@ -135,7 +135,7 @@ public $defaults = array(
return $this->{'MonitorIds'};
}
public static function get_group_dropdown() {
public static function get_group_dropdown( ) {
session_start();
$selected_group_id = 0;
@ -148,13 +148,23 @@ public $defaults = array(
}
session_write_close();
return htmlSelect( 'Group[]', Group::get_dropdown_options(), isset($_SESSION['Group'])?$_SESSION['Group']:null, array(
'onchange' => 'this.form.submit();',
'class'=>'chosen',
'multiple'=>'multiple',
'data-placeholder'=>'All',
) );
} # end public static function get_group_dropdown
public static function get_dropdown_options() {
$Groups = array();
foreach ( Group::find_all( ) as $Group ) {
$Groups[$Group->Id()] = $Group;
}
# This array is indexed by parent_id
global $children;
global $children;
$children = array();
foreach ( $Groups as $id=>$Group ) {
@ -181,16 +191,10 @@ global $children;
$group_options += get_options( $Group );
}
}
return htmlSelect( 'Group[]', $group_options, isset($_SESSION['Group'])?$_SESSION['Group']:null, array(
'onchange' => 'this.form.submit();',
'class'=>'chosen',
'multiple'=>'multiple',
'data-placeholder'=>'All',
) );
return $group_options;
}
} # end public static function get_group_dropdown
public static function get_group_dropdowns() {
public static function get_group_dropdowns( $selected = null ) {
# This will end up with the group_id of the deepest selection
$group_id = 0;
$depth = 0;
@ -205,6 +209,7 @@ global $children;
break;
$parent_group_ids = array();
if ( ! $selected ) {
$selected_group_id = 0;
if ( isset($_REQUEST['group'.$depth]) ) {
$selected_group_id = $group_id = $_SESSION['group'.$depth] = $_REQUEST['group'.$depth];
@ -213,6 +218,9 @@ global $children;
} else if ( isset($_REQUEST['filtering']) ) {
unset($_SESSION['group'.$depth]);
}
} else {
$selected_group_id = $selected;
}
foreach ( $Groups as $Group ) {
if ( ! isset( $groups[$depth] ) ) {
@ -280,6 +288,23 @@ $group_options[$Group->Id()] = str_repeat( '&nbsp;', $depth ) . $Group->Name();
return $monitor_id;
}
public function Parent( ) {
if ( $this->{'ParentId'} ) {
return new Group($this->{'ParentId'});
}
return null;
}
public function Parents() {
$Parents = array();
$Parent = $this->Parent();
while( $Parent ) {
array_unshift($Parents, $Parent);
$Parent = $Parent->Parent();
}
return $Parents;
}
} # end class Group

View File

@ -20,6 +20,9 @@ private $defaults = array(
'OutputContainer' => 'auto',
'ZoneCount' => 0,
'Triggers' => null,
'Type' => 'Ffmpeg',
'MaxFPS' => null,
'AlarmMaxFPS' => null,
);
private $status_fields = array(
'AnalysisFPS' => null,
@ -395,5 +398,15 @@ Logger::Debug("sending command to $url");
}
} // end if we are on the recording server
}
public function GroupIds( ) {
if ( !array_key_exists('GroupIds', $this) ) {
if ( array_key_exists('Id', $this) and $this->{'Id'} ) {
$this->{'GroupIds'} = dbFetchAll( 'SELECT GroupId FROM Groups_Monitors WHERE MonitorId=?', 'GroupId', array($this->{'Id'}) );
} else {
$this0->{'GroupIds'} = array();
}
}
return $this->{'GroupIds'};
}
} // end class Monitor
?>

View File

@ -271,36 +271,14 @@ if ( !empty($_REQUEST['mid']) && canView( 'Control', $_REQUEST['mid'] ) ) {
}
// Control capability actions, require control edit permissions
if ( canEdit( 'Control' ) ) {
if ( canEdit('Control') ) {
if ( $action == 'controlcap' ) {
if ( !empty($_REQUEST['cid']) ) {
$control = dbFetchOne( 'SELECT * FROM Controls WHERE Id = ?', NULL, array($_REQUEST['cid']) );
} else {
$control = array();
}
require_once( 'Control.php' );
$Control = new Control( !empty($_REQUEST['cid']) ? $_REQUEST['cid'] : null );
// Define a field type for anything that's not simple text equivalent
$types = array(
// Empty
);
$columns = getTableColumns( 'Controls' );
foreach ( $columns as $name=>$type ) {
if ( preg_match( '/^(Can|Has)/', $name ) ) {
$types[$name] = 'toggle';
}
}
$changes = getFormChanges( $control, $_REQUEST['newControl'], $types, $columns );
if ( count( $changes ) ) {
if ( !empty($_REQUEST['cid']) ) {
dbQuery( 'update Controls set '.implode( ', ', $changes ).' where Id = ?', array($_REQUEST['cid']) );
} else {
dbQuery( 'insert into Controls set '.implode( ', ', $changes ) );
//$_REQUEST['cid'] = dbInsertId();
}
$refreshParent = true;
}
//$changes = getFormChanges( $control, $_REQUEST['newControl'], $types, $columns );
$Control->save( $_REQUEST['newControl'] );
$refreshParent = true;
$view = 'none';
} elseif ( $action == 'delete' ) {
if ( isset($_REQUEST['markCids']) ) {
@ -310,8 +288,8 @@ if ( canEdit( 'Control' ) ) {
$refreshParent = true;
}
}
}
}
} // end if action
} // end if canEdit Controls
if ( isset($_REQUEST['object']) and $_REQUEST['object'] == 'Monitor' ) {
if ( $action == 'save' ) {
@ -471,7 +449,7 @@ if ( canEdit( 'Monitors' ) ) {
$mid = 0;
if ( !empty($_REQUEST['mid']) ) {
$mid = validInt($_REQUEST['mid']);
$monitor = dbFetchOne( 'SELECT * FROM Monitors WHERE Id = ?', NULL, array($mid) );
$monitor = dbFetchOne( 'SELECT * FROM Monitors WHERE Id=?', NULL, array($mid) );
if ( ZM_OPT_X10 ) {
$x10Monitor = dbFetchOne( 'SELECT * FROM TriggersX10 WHERE MonitorId=?', NULL, array($mid) );
@ -484,6 +462,7 @@ if ( canEdit( 'Monitors' ) ) {
$x10Monitor = array();
}
}
$Monitor = new Monitor( $monitor );
// Define a field type for anything that's not simple text equivalent
$types = array(
@ -519,6 +498,7 @@ if ( canEdit( 'Monitors' ) ) {
zmaControl( $monitor, 'stop' );
zmcControl( $monitor, 'stop' );
dbQuery( 'UPDATE Monitors SET '.implode( ', ', $changes ).' WHERE Id=?', array($mid) );
// Groups will be added below
if ( isset($changes['Name']) or isset($changes['StorageId']) ) {
$OldStorage = new Storage( $monitor['StorageId'] );
$saferOldName = basename( $monitor['Name'] );
@ -578,16 +558,25 @@ if ( canEdit( 'Monitors' ) ) {
mkdir( $Storage->Path().'/'.$mid, 0755 );
$saferName = basename($_REQUEST['newMonitor']['Name']);
symlink( $mid, $Storage->Path().'/'.$saferName );
if ( isset($_COOKIE['zmGroup']) ) {
dbQuery( 'INSERT INTO Groups_Monitors (GroupId,MonitorId) VALUES (?,?)', array($_COOKIE['zmGroup'],$mid) );
}
} else {
Error("Error saving new Monitor.");
return;
}
} else {
Error("Users with Monitors restrictions cannot create new monitors.");
return;
}
if ( count($_POST['newMonitor']['GroupIds']) != count($Monitor->GroupIds()) or array_diff($_POST['newMonitor']['GroupIds'], $Monitor->GroupIds() ) ) {
if ( $Monitor->Id() )
dbQuery('DELETE FROM Groups_Monitors WHERE MonitorId=?', null, array($Mid));
if ( isset($_POST['newMonitor']['GroupIds']) ) {
foreach ( $_POST['newMonitor']['GroupIds'] as $group_id ) {
dbQuery( 'INSERT INTO Groups_Monitors (GroupId,MonitorId) VALUES (?,?)', array($group_id, $mid) );
}
}
} // end if there has been a change of groups
$restart = true;
} # end if count(changes)

View File

@ -553,20 +553,19 @@ function htmlSelect( $name, $contents, $values, $behaviours=false ) {
}
}
$html = "<select name=\"$name\" id=\"$name\"$behaviourText>";
return "<select name=\"$name\" id=\"$name\"$behaviourText>".htmlOptions( $contents, $values ).'</select>';
}
function htmlOptions( $contents, $values ) {
$html = '';
foreach ( $contents as $value=>$text ) {
if ( is_array( $text ) )
$text = $text['Name'];
else if ( is_object( $text ) )
$text = $text->Name();
//for ( $i = 0; $i < count($contents); $i +=2 ) {
//$value = $contents[$i];
//$text = $contents[$i+1];
$selected = is_array( $values ) ? in_array( $value, $values ) : !strcmp($value, $values);
//Warning("Selected is $selected from $value and $values");
$html .= "<option value=\"$value\"".($selected?" selected=\"selected\"":'').">$text</option>";
}
$html .= '</select>';
return $html;
}
@ -634,6 +633,7 @@ function getFormChanges( $values, $newValues, $types=false, $columns=false ) {
if ( !isset($types[$key]) )
$types[$key] = false;
switch( $types[$key] ) {
case 'set' :
{
@ -695,6 +695,16 @@ function getFormChanges( $values, $newValues, $types=false, $columns=false ) {
}
break;
}
case 'toggle' :
if ( (!isset($values[$key])) or $values[$key] != $value ) {
if ( empty($value) ) {
$changes[$key] = "$key = 0";
} else {
$changes[$key] = "$key = 0";
//$changes[$key] = $key . ' = '.dbEscape(trim($value));
}
}
break;
default :
{
if ( !isset($values[$key]) || ($values[$key] != $value) ) {

View File

@ -180,6 +180,7 @@ xhtmlHeaders( __FILE__, translate('Console') );
<?php
for( $monitor_i = 0; $monitor_i < count($displayMonitors); $monitor_i += 1 ) {
$monitor = $displayMonitors[$monitor_i];
$Monitor = new Monitor($monitor);
?>
<tr id="<?php echo 'monitor_id-'.$monitor['Id'] ?>" title="<?php echo $monitor['Id'] ?>">
<?php
@ -209,7 +210,16 @@ $stream_available = canView('Stream') && $monitor['CaptureFPS'] && $monitor['Fun
<?php
}
?>
<td class="colName"><a <?php echo ($stream_available ? 'href="?view=watch&amp;mid='.$monitor['Id'].'">' : '>') . $monitor['Name'] ?></a><br/><?php echo $monitor['Status'] ?></td>
<td class="colName">
<a <?php echo ($stream_available ? 'href="?view=watch&amp;mid='.$monitor['Id'].'">' : '>') . $monitor['Name'] ?></a><br/>
<?php echo $monitor['Status'] ?><br/>
<?php echo implode('<br/>',
array_map(function($group_id){
$Group = new Group($group_id);
return implode(' &gt; ', array_map(function($Group){ return $Group->Name(); }, $Group->Parents()));
}, $Monitor->GroupIds() ) );
?>
</td>
<td class="colFunction">
<?php echo makePopupLink( '?view=function&amp;mid='.$monitor['Id'], 'zmFunction', 'function', '<span class="'.$fclass.'">'.translate('Fn'.$monitor['Function']).( empty($monitor['Enabled']) ? ', disabled' : '' ) .'</span>', canEdit( 'Monitors' ) ) ?><br/>
<?php

View File

@ -38,42 +38,95 @@ function evaluateLoadTimes() {
else if (avgFrac >= 0.2) currentDisplayInterval = (currentDisplayInterval * 1.50).toFixed(1);
else if (avgFrac >= 0.1) currentDisplayInterval = (currentDisplayInterval * 2.00).toFixed(1);
else currentDisplayInterval = (currentDisplayInterval * 2.50).toFixed(1);
currentDisplayInterval = Math.min(Math.max(currentDisplayInterval, 30), 10000); // limit this from about 30fps to .1 fps
// limit this from about 40fps to .1 fps
currentDisplayInterval = Math.min(Math.max(currentDisplayInterval, 40), 10000);
imageLoadTimesEvaluated=0;
setSpeed(speedIndex);
$('fps').innerHTML="Display refresh rate is " + (1000 / currentDisplayInterval).toFixed(1) + " per second, avgFrac=" + avgFrac.toFixed(3) + ".";
} // end evaluateLoadTimes()
function getFrame( monId, time ) {
var Frame = null;
for ( var event_id in events ) {
// Search for the event matching this time. Would be more efficient if we had events indexed by monitor
Event = events[event_id];
if ( Event.MonitorId != monId || Event.StartTimeSecs > time || Event.EndTimeSecs < time )
continue;
var duration = Event.EndTimeSecs - Event.StartTimeSecs;
var frame = parseInt((time - Event.StartTimeSecs)/(duration)*Object.keys(Event.FramesById).length)+1;
// Need to get frame by time, not some fun calc that assumes frames have the same mlength.
// Frames are not sorted.
for ( var frame_id in Event.FramesById ) {
if ( 0 ) {
if ( frame == 0 ) {
console.log("Found frame for time " + time );
console.log(Frame);
Frame = Event.FramesById[frame_id];
break;
}
frame --;
continue;
}
if (
Event.FramesById[frame_id].TimeStampSecs == time
|| (
Event.FramesById[frame_id].TimeStampSecs < time
&& (
(!Event.FramesById[frame_id].NextTimeStampSecs)
||
(Event.FramesById[frame_id].NextTimeStampSecs > time)
)
)
) {
Frame = Event.FramesById[frame_id];
break;
}
} // end foreach frame in the event.
if ( ! Frame ) {
console.log("Difn't find frame for " + time );
return null;
}
} // end foreach event
return Frame;
}
// time is seconds since epoch
function getImageSource( monId, time ) {
if ( liveMode == 1 ) {
var new_url = monitorImageObject[monId].src.replace(/rand=\d+/i, 'rand='+Math.floor((Math.random() * 1000000) ));
var new_url = monitorImageObject[monId].src.replace(
/rand=\d+/i,
'rand='+Math.floor((Math.random() * 1000000) )
);
if ( auth_hash ) {
// update auth hash
new_url = new_url.replace(/auth=[a-z0-9]+/i, 'auth='+auth_hash);
}
return new_url;
}
var Frame = getFrame(monId, time);
if ( Frame ) {
Event = events[Frame.EventId];
for ( var i=0, eIdlength = eId.length; i < eIdlength; i++ ) {
// Search for the event matching this time. Would be more efficient if we had events indexed by monitor
if ( eMonId[i] == monId && time >= eStartSecs[i] && time <= eEndSecs[i] ) {
var duration = eEndSecs[i]-eStartSecs[i];
var frame = parseInt((time - eStartSecs[i])/(duration)*eventFrames[i])+1;
var storage = Storage[eStorageId[i]];
if ( storage.ServerId ) {
var server = Servers[storage.ServerId];
if ( server ) {
//console.log( server.Hostname + " for event " + eId[i] );
return location.protocol + '//' + server.Hostname + '/index.php?view=image&eid=' + eId[i] + '&fid='+frame + "&width=" + monitorCanvasObj[monId].width + "&height=" + monitorCanvasObj[monId].height;
} else {
console.log("No server found for " + storage.ServerId );
}
var storage = Storage[Event.StorageId];
if ( storage.ServerId ) {
var server = Servers[storage.ServerId];
if ( server ) {
//console.log( server.Hostname + " for event " + eId[i] );
return location.protocol + '//' + server.Hostname +
//'/cgi-bin/zms?mode=jpeg&replay=single&event=' + event_id +
//'&frame='+Frame.FrameId +
'/index.php?view=image&eid=' + event_id + '&fid='+Frame.FrameId +
"&width=" + monitorCanvasObj[monId].width +
"&height=" + monitorCanvasObj[monId].height;
} else {
console.log("No server found for " + storage.ServerId );
}
//console.log("No storage found for " + eStorageId[i] );
return "index.php?view=image&eid=" + eId[i] + '&fid='+frame + "&width=" + monitorCanvasObj[monId].width + "&height=" + monitorCanvasObj[monId].height;
}
} // end for
//console.log("No storage found for " + eStorageId[i] );
return '/index.php?view=image&eid=' + Frame.EventId + '&fid='+Frame.FrameId + "&width=" + monitorCanvasObj[monId].width + "&height=" + monitorCanvasObj[monId].height;
return "/cgi-bin/zms?mode=jpeg&replay=single&event=" + Frame.EventId + '&frame='+Frame.FrameId + "&width=" + monitorCanvasObj[monId].width + "&height=" + monitorCanvasObj[monId].height;
} // end found Frame
return "no data";
}
@ -294,7 +347,7 @@ function drawGraph() {
canvas.height = cHeight;
if ( eId.length == 0 ) {
if ( Object.keys(events).length == 0 ) {
ctx.globalAlpha=1;
ctx.font= "40px Georgia";
ctx.fillStyle="white";
@ -308,24 +361,32 @@ function drawGraph() {
// first fill in the bars for the events (not alarms)
for(var i=0; i<eId.length; i++) { // Display all we loaded
for ( var event_id in events ) {
var Event = events[event_id];
var x1=parseInt( (eStartSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth) ; // round low end down
var x2=parseInt( (eEndSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth + 0.5 ) ; // round high end up to be sure consecutive ones connect
ctx.fillStyle=monitorColour[eMonId[i]];
// round low end down
var x1 = parseInt((Event.StartTimeSecs - minTimeSecs) / rangeTimeSecs * cWidth);
var x2 = parseInt((Event.EndTimeSecs - minTimeSecs) / rangeTimeSecs * cWidth + 0.5 ) ; // round high end up to be sure consecutive ones connect
ctx.fillStyle = monitorColour[Event.MonitorId];
ctx.globalAlpha = 0.2; // light color for background
ctx.clearRect(x1,monitorIndex[eMonId[i]]*rowHeight,x2-x1,rowHeight); // Erase any overlap so it doesn't look artificially darker
ctx.fillRect (x1,monitorIndex[eMonId[i]]*rowHeight,x2-x1,rowHeight);
}
for(var i=0; (i<fScore.length) && (maxScore>0); i++) {
// Now put in scored frames (if any)
var x1=parseInt( (fTimeFromSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth) ; // round low end down
var x2=parseInt( (fTimeToSecs[i] - minTimeSecs) / rangeTimeSecs * cWidth + 0.5 ) ; // round up
if(x2-x1 < 2) x2=x1+2; // So it is visible make them all at least this number of seconds wide
ctx.fillStyle=monitorColour[fMonId[i]];
ctx.globalAlpha = 0.4 + 0.6 * (1 - fScore[i]/maxScore); // Background is scaled but even lowest is twice as dark as the background
ctx.fillRect(x1,monitorIndex[fMonId[i]]*rowHeight,x2-x1,rowHeight);
}
ctx.clearRect(x1,monitorIndex[Event.MonitorId]*rowHeight,x2-x1,rowHeight); // Erase any overlap so it doesn't look artificially darker
ctx.fillRect(x1,monitorIndex[Event.MonitorId]*rowHeight,x2-x1,rowHeight);
for ( var frame_id in Event.FramesById ) {
var Frame = Event.FramesById[frame_id];
if ( ! Frame.Score )
continue;
// Now put in scored frames (if any)
var x1=parseInt( (Frame.TimeStampSecs - minTimeSecs) / rangeTimeSecs * cWidth) ; // round low end down
var x2=parseInt( (Frame.TimeStampSecs - minTimeSecs) / rangeTimeSecs * cWidth + 0.5 ) ; // round up
if(x2-x1 < 2) x2=x1+2; // So it is visible make them all at least this number of seconds wide
ctx.fillStyle=monitorColour[Event.MonitorId];
ctx.globalAlpha = 0.4 + 0.6 * (1 - Frame.Score/maxScore); // Background is scaled but even lowest is twice as dark as the background
ctx.fillRect(x1,monitorIndex[Event.MonitorId]*rowHeight,x2-x1,rowHeight);
} // end foreach frame
} // end foreach Event
for(var i=0; i<numMonitors; i++) {
// Note that this may be a sparse array
ctx.font= parseInt(rowHeight * timeLabelsFractOfRow).toString() + "px Georgia";
@ -343,7 +404,8 @@ function redrawScreen() {
// if we are not in live view switch to history -- this has to come before fit in case we re-establish the timeline
$('DateTimeDiv').style.display="none";
$('SpeedDiv').style.display="none";
$('timelinediv').style.display="none";
var timelinediv= $('timelinediv');
if ( timelinediv ) timelinediv.style.display="none";
$('liveButton').innerHTML="History";
$('zoomin').style.display="none";
$('zoomout').style.display="none";
@ -368,6 +430,7 @@ function redrawScreen() {
$('panright').style.display="inline";
$('panright').style.display="inline-flex";
if ($('downloadVideo')) $('downloadVideo').style.display="inline";
drawGraph();
}
if ( fitMode == 1 ) {
@ -391,7 +454,6 @@ function redrawScreen() {
$('fit').innerHTML="Fit";
setScale(currentScale);
}
drawGraph();
outputUpdate(currentTimeSecs);
timerFire(); // force a fire in case it's not timing
}
@ -709,28 +771,15 @@ function showOneMonitor(monId) {
url="?view=watch&mid=" + monId.toString();
createPopup(url, 'zmWatch', 'watch', monitorWidth[monId], monitorHeight[monId] );
} else {
for ( var i=0, len=eId.length; i<len; i++ ) {
if ( eMonId[i] != monId )
continue;
if ( currentTimeSecs >= eStartSecs[i] && currentTimeSecs <= eEndSecs[i] ) {
url="?view=event&eid=" + eId[i] + '&fid=' + parseInt(Math.max(1, Math.min(eventFrames[i], eventFrames[i] * (currentTimeSecs - eStartSecs[i]) / (eEndSecs[i] - eStartSecs[i] + 1) ) ));
break;
} else if ( currentTimeSecs <= eStartSecs[i] ) {
if ( i ) {
// Didn't find an exact event, so go with the one before.
url="?view=event&eid=" + eId[i] + '&fid=' + parseInt(Math.max(1, Math.min(eventFrames[i], eventFrames[i] * (currentTimeSecs - eStartSecs[i]) / (eEndSecs[i] - eStartSecs[i] + 1) ) ));
}
break;
}
}
if ( url ) {
createPopup(url, 'zmEvent', 'event', monitorWidth[monId], monitorHeight[monId]);
var Frame = getFrame( monId, currentTimeSecs );
if ( Frame ) {
url="?view=event&eid=" + Frame.EventId + '&fid=' +Frame.FrameId;
createPopup(url, 'zmEvent', 'event', monitorWidth[monId], monitorHeight[monId]);
} else {
url="?view=watch&mid=" + monId.toString();
createPopup(url, 'zmWatch', 'watch', monitorWidth[monId], monitorHeight[monId] );
}
}
} // end if live/events
}
function zoom(monId,scale) {
@ -787,8 +836,6 @@ function changeDateTime(e) {
// >>>>>>>>> Initialization that runs on window load by being at the bottom
function initPage() {
canvas = $("timeline");
ctx = canvas.getContext('2d');
for ( var i = 0, len = monitorPtr.length; i < len; i += 1 ) {
var monId = monitorPtr[i];
if ( ! monId ) continue;
@ -804,7 +851,11 @@ function initPage() {
loadImage2Monitor( monId, monitorImageURL[monId] );
}
}
drawGraph();
if ( !liveMode ) {
canvas = $("timeline");
ctx = canvas.getContext('2d');
drawGraph();
}
setSpeed(speedIndex);
//setFit(fitMode); // will redraw
//setLive(liveMode); // will redraw

View File

@ -9,10 +9,14 @@ echo $offset . '; // ' . floor($offset / 3600) . ' hours ';
var currentScale=<?php echo $defaultScale?>;
var liveMode=<?php echo $liveMode?>;
var fitMode=<?php echo $fitMode?>;
var currentSpeed=<?php echo $speeds[$speedIndex]?>; // slider scale, which is only for replay and relative to real time
// slider scale, which is only for replay and relative to real time
var currentSpeed=<?php echo $speeds[$speedIndex]?>;
var speedIndex=<?php echo $speedIndex?>;
// will be set based on performance, this is the display interval in milliseconds for history, and fps for live, and dynamically determined (in ms)
// will be set based on performance, this is the display interval in milliseconds
// for history, and fps for live, and dynamically determined (in ms)
var currentDisplayInterval=<?php echo $initialDisplayInterval?>;
var playSecsperInterval=1; // How many seconds of recorded image we play per refresh determined by speed (replay rate) and display interval; (default=1 if coming from live)
var timerInterval; // milliseconds between interrupts
@ -21,12 +25,6 @@ var freeTimeLastIntervals=[]; // Percentage of current interval used in loadi
var imageLoadTimesEvaluated=0; // running count
var imageLoadTimesNeeded=15; // and how many we need
var timeLabelsFractOfRow = 0.9;
var eMonId = [];
var eId = [];
var eStorageId = [];
var eStartSecs = [];
var eEndSecs = [];
var eventFrames = []; // this is going to presume all frames equal durationlength
<?php
@ -41,6 +39,7 @@ if ( ! $minTimeSecs )
$index = 0;
$anyAlarms = false;
$maxScore=0;
if ( ! $liveMode ) {
$result = dbQuery( $eventsSql );
@ -49,27 +48,53 @@ if ( ! $liveMode ) {
return;
}
$EventsById = array();
while( $event = $result->fetch( PDO::FETCH_ASSOC ) ) {
$event_id = $event['Id'];
$EventsById[$event_id] = $event;
}
$StartTimeSecs = strtotime($event['StartTime']);
$EndTimeSecs = strtotime($event['EndTime']);
$next_frames = array();
if ( $minTimeSecs > $StartTimeSecs ) $minTimeSecs = $StartTimeSecs;
if ( $result = dbQuery($frameSql) ) {
$next_frame = null;
while( $frame = $result->fetch(PDO::FETCH_ASSOC) ) {
$event_id = $frame['EventId'];
$event = &$EventsById[$event_id];
$frame['TimeStampSecs'] = $event['StartTimeSecs'] + $frame['Delta'];
if ( !isset($event['FramesById']) ) {
$event['FramesById'] = array();
$frame['NextTimeStampSecs'] = 0;
} else {
$frame['NextTimeStampSecs'] = $next_frames[$frame['EventId']]['TimeStampSecs'];;
}
$event['FramesById'] += array( $frame['Id']=>$frame );
$next_frames[$frame['EventId']] = $frame;
}
}
echo "var events = {\n";
foreach ( $EventsById as $event_id=>$event ) {
$StartTimeSecs = $event['StartTimeSecs'];
$EndTimeSecs = $event['EndTimeSecs'];
if ( $minTimeSecs > $StartTimeSecs ) $minTimeSecs = $StartTimeSecs;
if ( $maxTimeSecs < $EndTimeSecs ) $maxTimeSecs = $EndTimeSecs;
echo "
eMonId[$index]=" . $event['MonitorId'] . ";
eStorageId[$index]=".$event['StorageId'] . ";
eId[$index]=" . $event['Id'] . ";
eStartSecs[$index]=" . $StartTimeSecs . ";
eEndSecs[$index]=" . $EndTimeSecs . ";
eventFrames[$index]=" . $event['Frames'] . ";
";
$event_json = json_encode($event, JSON_PRETTY_PRINT);
echo " $event_id : $event_json,\n";
$index = $index + 1;
if ( $event['MaxScore'] > 0 )
if ( $event['MaxScore'] > 0 ) {
if ( $event['MaxScore'] > $maxScore )
$maxScore = $event['MaxScore'];
$anyAlarms = true;
}
}
echo " };\n";
// if there is no data set the min/max to the passed in values
if ( $index == 0 ) {
@ -93,60 +118,6 @@ eventFrames[$index]=" . $event['Frames'] . ";
$maxTimeSecs = strtotime($maxTime);
}
// If we had any alarms in those events, this builds the list of all alarm frames, but consolidated down to (nearly) contiguous segments
// comparison in else governs how aggressively it consolidates
echo "
var fMonId = [];
var fTimeFromSecs = [];
var fTimeToSecs = [];
var fScore = [];
";
$maxScore=0;
$index=0;
$mId=-1;
$fromSecs=-1;
$toSecs=-1;
$maxScore=-1;
if ( $anyAlarms && $result = dbQuery( $frameSql ) ) {
while( $frame = $result->fetch( PDO::FETCH_ASSOC ) ) {
if ( $mId < 0 ) {
$mId = $frame['MonitorId'];
$fromSecs = $frame['TimeStampSecs'];
$toSecs = $frame['TimeStampSecs'];
$maxScore = $frame['Score'];
} else if ( $mId != $frame['MonitorId'] || $frame['TimeStampSecs'] - $toSecs > 10 ) {
// dump this one start a new
$index++;
echo "
fMonId[$index]= $mId;
fTimeFromSecs[$index]= $fromSecs;
fTimeToSecs[$index]= $toSecs;
fScore[$index]= $maxScore;
";
$mId = $frame['MonitorId'];
$fromSecs = $frame['TimeStampSecs'];
$toSecs = $frame['TimeStampSecs'];
$maxScore = $frame['Score'];
} else {
// just add this one on
$toSecs = $frame['TimeStampSecs'];
if ( $maxScore < $frame['Score'] ) $maxScore = $frame['Score'];
}
}
}
if ( $mId > 0 ) {
echo "
fMonId[$index]= $mId;
fTimeFromSecs[$index]= $fromSecs;
fTimeToSecs[$index]= $toSecs;
fScore[$index]= $maxScore;
";
}
echo "var maxScore=$maxScore;\n"; // used to skip frame load if we find no alarms.
} // end if initialmodeislive

View File

@ -89,12 +89,12 @@ if ( ! $monitor ) {
'Pass' => '',
'Colours' => 4,
'Palette' => 0,
'Width' => '1280',
'Height' => '962',
'Width' => '',
'Height' => '',
'Orientation' => '0',
'Deinterlacing' => 0,
'RTSPDescribe' => 0,
'SaveJPEGs' => '4',
'SaveJPEGs' => '0',
'VideoWriter' => '1',
'EncoderParameters' => "# Lines beginning with # are a comment \n# For changing quality, use the crf option\n# 1 is best, 51 is worst quality\n#crf=23\n",
'RecordAudio' => '0',
@ -102,9 +102,9 @@ if ( ! $monitor ) {
'LabelX' => 0,
'LabelY' => 0,
'LabelSize' => 1,
'ImageBufferCount' => 40,
'WarmupCount' => 1,
'PreEventCount' => 1,
'ImageBufferCount' => 20,
'WarmupCount' => 0,
'PreEventCount' => 0,
'PostEventCount' => 5,
'StreamReplayBuffer' => 0,
'AlarmFrameCount' => 1,
@ -539,7 +539,7 @@ foreach ( $tabs as $name=>$value ) {
?>
</ul>
<div class="clear"></div>
<form name="contentForm" id="contentForm" method="post" action="<?php echo $_SERVER['PHP_SELF'] ?>" onsubmit="return validateForm( this )">
<form name="contentForm" id="contentForm" method="post" action="<?php echo $_SERVER['PHP_SELF'] ?>" onsubmit="if(validateForm(this)){$j('#contentButtons').hide();return true;}else{return false;};">
<input type="hidden" name="view" value="<?php echo $view ?>"/>
<input type="hidden" name="tab" value="<?php echo $tab ?>"/>
<input type="hidden" name="action" value="monitor"/>
@ -731,7 +731,7 @@ switch ( $tab ) {
<tr>
<td><?php echo translate('LinkedMonitors') ?></td>
<td>
<select name="monitorIds" size="4" multiple="multiple" onchange="updateLinkedMonitors( this )">
<select name="monitorIds" class="chosen" multiple="multiple" onchange="updateLinkedMonitors( this )">
<?php
$monitors = dbFetchAll( 'select Id,Name from Monitors order by Sequence asc' );
if ( $monitor->LinkedMonitors() )
@ -749,6 +749,9 @@ switch ( $tab ) {
</select>
</td>
</tr>
<tr><td><?php echo translate('Groups'); ?></td><td><select name="newMonitor[GroupIds][]" multiple="multiple" class="chosen"><?php
echo htmlOptions(Group::get_dropdown_options( ), $monitor->GroupIds() );
?></td></tr>
<tr><td><?php echo translate('AnalysisFPS') ?></td><td><input type="text" name="newMonitor[AnalysisFPSLimit]" value="<?php echo validHtmlStr($monitor->AnalysisFPSLimit()) ?>" size="6"/></td></tr>
<?php
if ( $monitor->Type() != 'Local' && $monitor->Type() != 'File' && $monitor->Type() != 'NVSocket' ) {
@ -1034,11 +1037,14 @@ if ( $monitor->Type() == 'Local' ) {
</tbody>
</table>
<div id="contentButtons">
<input type="submit" value="<?php echo translate('Save') ?>"<?php if ( !canEdit( 'Monitors' ) ) { ?> disabled="disabled"<?php } ?>/>
<input type="button" value="<?php echo translate('Cancel') ?>" onclick="closeWindow()"/>
<button type="submit" value="Save"<?php echo canEdit('Monitors') ? '' : ' disabled="disabled"' ?>><?php echo translate('Save') ?></button>
<button onclick="closeWindow()"><?php echo translate('Cancel') ?></button>
</div>
</form>
</div>
</div>
</body>
<script type="text/javascript">
$j('.chosen').chosen();
</script>
</html>

View File

@ -94,14 +94,14 @@ if (isset($_REQUEST['minTime']) && isset($_REQUEST['maxTime']) && count($display
// if the bulk record has not been written - to be able to include more current frames reduce bulk frame sizes (event size can be large)
// Note we round up just a bit on the end time as otherwise you get gaps, like 59.78 to 00 in the next second, which can give blank frames when moved through slowly.
$eventsSql = '
SELECT E.Id,E.Name,E.StorageId,E.StartTime AS StartTime,
CASE WHEN E.EndTime IS NULL THEN (SELECT NOW()) ELSE E.EndTime END AS EndTime,
E.Length,
CASE WHEN E.Frames IS NULL THEN (SELECT COUNT(*) FROM Frames F WHERE F.EventId=E.Id) ELSE E.Frames END AS Frames,
E.MaxScore,E.Cause,E.Notes,E.Archived,E.MonitorId
$eventsSql = 'SELECT
E.Id,E.Name,E.StorageId,
E.StartTime AS StartTime,UNIX_TIMESTAMP(E.StartTime) AS StartTimeSecs,
CASE WHEN E.EndTime IS NULL THEN (SELECT NOW()) ELSE E.EndTime END AS EndTime,
UNIX_TIMESTAMP(EndTime) AS EndTimeSecs,
E.Length, E.Frames, E.MaxScore,E.Cause,E.Notes,E.Archived,E.MonitorId
FROM Events AS E
WHERE NOT isnull(E.Frames)
WHERE 1 > 0
';
// select E.Id,E.Name,UNIX_TIMESTAMP(E.StartTime) as StartTimeSecs,UNIX_TIMESTAMP(max(DATE_ADD(E.StartTime, Interval Delta+0.5 Second))) as CalcEndTimeSecs, E.Length,max(F.FrameId) as Frames,E.MaxScore,E.Cause,E.Notes,E.Archived,E.MonitorId
@ -112,10 +112,9 @@ $eventsSql = '
// Note that the delta value seems more accurate than the time stamp for some reason.
$frameSql = '
SELECT E.Id AS eId, E.MonitorId, UNIX_TIMESTAMP(DATE_ADD(E.StartTime, Interval Delta Second)) AS TimeStampSecs, max(F.Score) AS Score
FROM Events AS E
INNER JOIN Frames AS F ON (F.EventId = E.Id)
WHERE F.Score>0
SELECT Id, FrameId, EventId, TimeStamp, UNIX_TIMESTAMP(TimeStamp) AS TimeStampSecs, Score, Delta
FROM Frames
WHERE EventId IN (SELECT E.Id FROM Events AS E WHERE 1>0
';
// This program only calls itself with the time range involved -- it does all monitors (the user can see, in the called group) all the time
@ -183,7 +182,7 @@ if ( isset($_REQUEST['current']) )
$liveMode = 1; // default to live
if ( isset($_REQUEST['live']) && $_REQUEST['live']=='0' )
$liveMode=0;
$liveMode = 0;
$initialDisplayInterval = 1000;
if ( isset($_REQUEST['displayinterval']) )
@ -196,10 +195,14 @@ if ( isset($minTime) && isset($maxTime) ) {
$maxTimeSecs = strtotime($maxTime);
Logger::Debug("Min/max time secs: $minTimeSecs $maxTimeSecs");
$eventsSql .= " AND EndTime > '" . $minTime . "' AND StartTime < '" . $maxTime . "'";
$frameSql .= " AND TimeStamp > '" . $minTime . "' AND TimeStamp < '" . $maxTime . "'";
$frameSql .= " AND EndTime > '" . $minTime . "' AND StartTime < '" . $maxTime . "'";
$frameSql .= ") AND TimeStamp > '" . $minTime . "' AND TimeStamp < '" . $maxTime . "'";
}
$frameSql .= ' GROUP BY E.Id, E.MonitorId, F.TimeStamp, F.Delta ORDER BY E.MonitorId, F.TimeStamp ASC';
#$frameSql .= ' GROUP BY E.Id, E.MonitorId, F.TimeStamp, F.Delta ORDER BY E.MonitorId, F.TimeStamp ASC';
#$frameSql .= ' GROUP BY E.Id, E.MonitorId, F.TimeStamp, F.Delta ORDER BY E.MonitorId, F.TimeStamp ASC';
$eventsSql .= ' ORDER BY E.Id ASC';
// DESC is intentional. We process them in reverse order so that we can point each frame to the next one in time.
$frameSql .= ' ORDER BY Id DESC';
$monitors = array();
foreach( $displayMonitors as $row ) {

View File

@ -223,11 +223,17 @@ foreach( array_map( 'basename', glob('skins/'.$current_skin.'/css/*',GLOB_ONLYDI
<tr>
<td class="colName"><?php echo makePopupLink( '?view=server&amp;id='.$row['Id'], 'zmServer', 'server', validHtmlStr($row['Name']), $canEdit ) ?></td>
<td class="colHostname"><?php echo makePopupLink( '?view=server&amp;id='.$row['Id'], 'zmServer', 'server', validHtmlStr($row['Hostname']), $canEdit ) ?></td>
<td class="colStatus"><?php echo makePopupLink( '?view=server&amp;id='.$row['Id'], 'zmServer', 'server', validHtmlStr($row['Status']), $canEdit ) ?></td>
<td class="colStatus
<?php if ( $row['Status'] == 'NotRunning' ) { echo 'danger'; } ?>
"><?php echo makePopupLink( '?view=server&amp;id='.$row['Id'], 'zmServer', 'server', validHtmlStr($row['Status']), $canEdit ) ?></td>
<td class="colMonitorCount"><?php echo makePopupLink( '?view=server&amp;id='.$row['Id'], 'zmServer', 'server', validHtmlStr($row['MonitorCount']), $canEdit ) ?></td>
<td class="colCpuLoad"><?php echo makePopupLink( '?view=server&amp;id='.$row['Id'], 'zmServer', 'server',$row['CpuLoad'], $canEdit ) ?></td>
<td class="colMemory"><?php echo makePopupLink( '?view=server&amp;id='.$row['Id'], 'zmServer', 'server', human_filesize($row['FreeMem']) . ' / ' . human_filesize($row['TotalMem']), $canEdit ) ?></td>
<td class="colSwap"><?php echo makePopupLink( '?view=server&amp;id='.$row['Id'], 'zmServer', 'server', human_filesize($row['FreeSwap']) . ' / ' . human_filesize($row['TotalSwap']) , $canEdit ) ?></td>
<td class="colCpuLoad
<?php if ( $row['CpuLoad'] > 5 ) { echo 'danger'; } ?>
"><?php echo makePopupLink( '?view=server&amp;id='.$row['Id'], 'zmServer', 'server',$row['CpuLoad'], $canEdit ) ?></td>
<td class="colMemory
<?php if ( $row['FreeMem']/$row['TotalMem'] < .1 ) { echo 'danger'; } ?>"><?php echo makePopupLink( '?view=server&amp;id='.$row['Id'], 'zmServer', 'server', human_filesize($row['FreeMem']) . ' / ' . human_filesize($row['TotalMem']), $canEdit ) ?></td>
<td class="colSwap
<?php if ( $row['FreeSwap']/$row['TotalSwap'] < .1 ) { echo 'danger'; } ?>"><?php echo makePopupLink( '?view=server&amp;id='.$row['Id'], 'zmServer', 'server', human_filesize($row['FreeSwap']) . ' / ' . human_filesize($row['TotalSwap']) , $canEdit ) ?></td>
<td class="colStats"><?php echo makePopupLink( '?view=server&amp;id='.$row['Id'], 'zmServer', 'server', $row['zmstats'] ? 'yes' : 'no', $canEdit ) ?></td>
<td class="colAudit"><?php echo makePopupLink( '?view=server&amp;id='.$row['Id'], 'zmServer', 'server', $row['zmaudit'] ? 'yes' : 'no', $canEdit ) ?></td>
<td class="colTrigger"><?php echo makePopupLink( '?view=server&amp;id='.$row['Id'], 'zmServer', 'server', $row['zmtrigger'] ? 'yes' : 'no', $canEdit ) ?></td>