Merge branch 'master' into replace_function_concept
This commit is contained in:
commit
f66a463574
|
@ -83,6 +83,7 @@ mark_as_advanced(
|
|||
ZM_TARGET_DISTRO
|
||||
ZM_PATH_MAP
|
||||
ZM_PATH_ARP
|
||||
ZM_PATH_ARP_SCAN
|
||||
ZM_CONFIG_DIR
|
||||
ZM_CONFIG_SUBDIR
|
||||
ZM_SYSTEMD
|
||||
|
@ -145,6 +146,8 @@ set(ZM_PATH_MAP "/dev/shm" CACHE PATH
|
|||
"Location to save mapped memory files, default: /dev/shm")
|
||||
set(ZM_PATH_ARP "" CACHE PATH
|
||||
"Full path to compatible arp binary. Leave empty for automatic detection.")
|
||||
set(ZM_PATH_ARP_SCAN "" CACHE PATH
|
||||
"Full path to compatible scan_arp binary. Leave empty for automatic detection.")
|
||||
set(ZM_CONFIG_DIR "/${CMAKE_INSTALL_SYSCONFDIR}" CACHE PATH
|
||||
"Location of ZoneMinder configuration, default system config directory")
|
||||
set(ZM_CONFIG_SUBDIR "${ZM_CONFIG_DIR}/conf.d" CACHE PATH
|
||||
|
@ -641,6 +644,18 @@ if(ZM_PATH_ARP STREQUAL "")
|
|||
endif()
|
||||
endif()
|
||||
|
||||
# Find the path to an arp-scan compatible executable
|
||||
if(ZM_PATH_ARP_SCAN STREQUAL "")
|
||||
find_program(ARP_SCAN_EXECUTABLE arp-scan)
|
||||
if(ARP_SCAN_EXECUTABLE)
|
||||
set(ZM_PATH_ARP_SCAN "${ARP_SCAN_EXECUTABLE}")
|
||||
mark_as_advanced(ARP_SCAN_EXECUTABLE)
|
||||
endif()
|
||||
if(ARP_SCAN_EXECUTABLE-NOTFOUND)
|
||||
message(WARNING "Unable to find a compatible arp-scan binary. Monitor probe will be less powerful.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Some variables that zm expects
|
||||
set(ZM_PID "${ZM_RUNDIR}/zm.pid")
|
||||
set(ZM_CONFIG "${ZM_CONFIG_DIR}/zm.conf")
|
||||
|
|
|
@ -47,5 +47,9 @@ ZM_PATH_SWAP=@ZM_TMPDIR@
|
|||
# ZoneMinder will find the arp binary automatically on most systems
|
||||
ZM_PATH_ARP="@ZM_PATH_ARP@"
|
||||
|
||||
# Full path to optional arp-scan binary
|
||||
# ZoneMinder will find the arp-scan binary automatically on most systems
|
||||
ZM_PATH_ARP_SCAN="@ZM_PATH_ARP_SCAN@"
|
||||
|
||||
#Full path to shutdown binary
|
||||
ZM_PATH_SHUTDOWN="@ZM_PATH_SHUTDOWN@"
|
||||
|
|
|
@ -7,6 +7,8 @@ configure_file(logrotate.conf.in "${CMAKE_CURRENT_BINARY_DIR}/logrotate.conf" @O
|
|||
configure_file(syslog.conf.in "${CMAKE_CURRENT_BINARY_DIR}/syslog.conf" @ONLY)
|
||||
configure_file(com.zoneminder.systemctl.policy.in "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.policy" @ONLY)
|
||||
configure_file(com.zoneminder.systemctl.rules.in "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.rules" @ONLY)
|
||||
configure_file(com.zoneminder.arp-scan.policy.in "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.arp-scan.policy" @ONLY)
|
||||
configure_file(com.zoneminder.arp-scan.rules.in "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.arp-scan.rules" @ONLY)
|
||||
configure_file(zoneminder.service.in "${CMAKE_CURRENT_BINARY_DIR}/zoneminder.service" @ONLY)
|
||||
configure_file(zoneminder-tmpfiles.conf.in "${CMAKE_CURRENT_BINARY_DIR}/zoneminder-tmpfiles.conf" @ONLY)
|
||||
configure_file(zoneminder.desktop.in "${CMAKE_CURRENT_BINARY_DIR}/zoneminder.desktop" @ONLY)
|
||||
|
@ -19,6 +21,8 @@ configure_file(zm-sudo.in "${CMAKE_CURRENT_BINARY_DIR}/zm-sudo" @ONLY)
|
|||
if(WITH_SYSTEMD)
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.policy" DESTINATION "${PC_POLKIT_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/polkit-1/actions")
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.systemctl.rules" DESTINATION "${PC_POLKIT_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/polkit-1/rules.d")
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.arp-scan.policy" DESTINATION "${PC_POLKIT_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/polkit-1/actions")
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.zoneminder.arp-scan.rules" DESTINATION "${PC_POLKIT_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/polkit-1/rules.d")
|
||||
endif()
|
||||
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zoneminder.desktop" DESTINATION "${CMAKE_INSTALL_DATADIR}/applications")
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE policyconfig PUBLIC
|
||||
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
|
||||
<policyconfig>
|
||||
|
||||
<vendor>The ZoneMinder Project</vendor>
|
||||
<vendor_url>http://www.zoneminder.com/</vendor_url>
|
||||
|
||||
<action id="com.zoneminder.policykit.pkexec.run-arp-scan">
|
||||
<description>Allow the ZoneMinder webuser to run arp-scan</description>
|
||||
<message>The ZoneMinder webuser is trusted to run arp-scan</message>
|
||||
<defaults>
|
||||
<allow_any>yes</allow_any>
|
||||
<allow_inactive>yes</allow_inactive>
|
||||
<allow_active>yes</allow_active>
|
||||
</defaults>
|
||||
<annotate key="org.freedesktop.policykit.exec.path">/usr/sbin/arp-scan</annotate>
|
||||
</action>
|
||||
|
||||
</policyconfig>
|
|
@ -0,0 +1,7 @@
|
|||
polkit.addRule(function(action, subject) {
|
||||
if (action.id == "com.zoneminder.policykit.pkexec.run-arp-scan" &&
|
||||
subject.user != "@WEB_USER@") {
|
||||
return polkit.Result.NO;
|
||||
}
|
||||
|
||||
});
|
|
@ -194,7 +194,6 @@ sub getCamParams {
|
|||
}
|
||||
}
|
||||
|
||||
#autoStop
|
||||
#This makes use of the ZoneMinder Auto Stop Timeout on the Control Tab
|
||||
sub autoStop {
|
||||
my $self = shift;
|
||||
|
@ -202,13 +201,19 @@ sub autoStop {
|
|||
|
||||
if ( $autostop ) {
|
||||
Debug('Auto Stop');
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $msg = '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . '<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>' . $profileToken . '</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);
|
||||
|
||||
my $cmd = 'onvif/PTZ';
|
||||
my $content_type = 'application/soap+xml; charset=utf-8; action="http://www.onvif.org/ver20/ptz/wsdl/ContinuousMove"';
|
||||
|
||||
my $msg ='<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">'.((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '').'<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>' . $profileToken . '</ProfileToken><Velocity><PanTilt x="0" y="0" xmlns="http://www.onvif.org/ver10/schema"/></Velocity></ContinuousMove></s:Body></s:Envelope>';
|
||||
$self->sendCmd($cmd, $msg, $content_type);
|
||||
|
||||
# Reported to not work, so superceded by the cmd above
|
||||
$msg = '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">' . ((%identity) ? authentificationHeader($identity{username}, $identity{password}) : '') . '<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>' . $profileToken . '</ProfileToken><PanTilt>true</PanTilt><Zoom>false</Zoom></Stop></s:Body></s:Envelope>';
|
||||
$self->sendCmd($cmd, $msg, $content_type);
|
||||
}
|
||||
}
|
||||
} # end sub autoStop
|
||||
|
||||
# Reset the Camera
|
||||
sub reset {
|
||||
|
|
|
@ -28,6 +28,7 @@ our %EXPORT_TAGS = (
|
|||
makePath
|
||||
jsonEncode
|
||||
jsonDecode
|
||||
jsonLoad
|
||||
systemStatus
|
||||
packageControl
|
||||
daemonControl
|
||||
|
@ -536,6 +537,23 @@ sub jsonDecode {
|
|||
return $result;
|
||||
}
|
||||
|
||||
sub jsonLoad {
|
||||
my $file = shift;
|
||||
my $json = undef;
|
||||
eval {
|
||||
require File::Slurp;
|
||||
my $contents = File::Slurp::read_file($file);
|
||||
if (!$contents) {
|
||||
Error("No contents for $file");
|
||||
return $json;
|
||||
}
|
||||
require JSON;
|
||||
$json = JSON::decode_json($contents);
|
||||
};
|
||||
Error($@) if $@;
|
||||
return $json;
|
||||
}
|
||||
|
||||
sub parseNameEqualsValueToHash {
|
||||
my %settings;
|
||||
foreach my $line ( split ( /\r?\n/, $_[0] ) ) {
|
||||
|
|
|
@ -464,15 +464,18 @@ sub generateImage {
|
|||
my $event_path = $Event->Path();
|
||||
my $capture_image_path = sprintf('%s/%0'.$Config{ZM_EVENT_IMAGE_DIGITS}.'d-capture.jpg', $event_path, $frame->{FrameId});
|
||||
my $analyse_image_path = sprintf('%s/%0'.$Config{ZM_EVENT_IMAGE_DIGITS}.'d-analyse.jpg', $event_path, $frame->{FrameId}) if $analyse;
|
||||
my $video_path = sprintf('%s/%d-video.mp4', $event_path, $Event->{Id});
|
||||
my $video_path = sprintf('%s/%s', $event_path, $Event->{DefaultVideo});
|
||||
my $image_path = '';
|
||||
|
||||
# check if the image file exists. If the file doesn't exist and we use H264 try to extract it from .mp4 video
|
||||
if ( $analyse && -r $analyse_image_path ) {
|
||||
Debug("Using analysis and jpeg exists $analyse_image_path");
|
||||
$image_path = $analyse_image_path;
|
||||
} elsif ( -r $capture_image_path ) {
|
||||
Debug("Using captures and jpeg exists $capture_image_path");
|
||||
$image_path = $capture_image_path;
|
||||
} elsif ( -r $video_path ) {
|
||||
Debug("mp4 exists $video_path");
|
||||
my $command ="ffmpeg -nostdin -ss $$frame{Delta} -i '$video_path' -frames:v 1 '$capture_image_path'";
|
||||
#$command = "ffmpeg -y -v 0 -i $video_path -vf 'select=gte(n\\,$$frame{FrameId}),setpts=PTS-STARTPTS' -vframes 1 -f image2 $capture_image_path";
|
||||
my $output = qx($command);
|
||||
|
@ -486,6 +489,8 @@ sub generateImage {
|
|||
} else {
|
||||
$image_path = $capture_image_path;
|
||||
}
|
||||
} else {
|
||||
Debug("No files found at $analyse_image_path, $capture_image_path or $video_path");
|
||||
}
|
||||
return $image_path;
|
||||
}
|
||||
|
@ -723,7 +728,7 @@ sub substituteTags {
|
|||
if ( -e $path ) {
|
||||
push @$attachments_ref, { type=>'image/jpeg', path=>$path };
|
||||
} else {
|
||||
Warning("Path to first image does not exist at $path");
|
||||
Warning("Path to first image does not exist at $path for image $first_alarm_frame");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,6 +33,9 @@ set(ZM_BIN_SRC_FILES
|
|||
zm_libvnc_camera.cpp
|
||||
zm_local_camera.cpp
|
||||
zm_monitor.cpp
|
||||
zm_monitor_monitorlink.cpp
|
||||
zm_monitor_janus.cpp
|
||||
zm_monitor_amcrest.cpp
|
||||
zm_monitorstream.cpp
|
||||
zm_ffmpeg.cpp
|
||||
zm_ffmpeg_camera.cpp
|
||||
|
|
|
@ -141,174 +141,6 @@ std::string TriggerState_Strings[] = {
|
|||
"Cancel", "On", "Off"
|
||||
};
|
||||
|
||||
Monitor::MonitorLink::MonitorLink(unsigned int p_id, const char *p_name) :
|
||||
id(p_id),
|
||||
shared_data(nullptr),
|
||||
trigger_data(nullptr),
|
||||
video_store_data(nullptr)
|
||||
{
|
||||
strncpy(name, p_name, sizeof(name)-1);
|
||||
|
||||
#if ZM_MEM_MAPPED
|
||||
map_fd = -1;
|
||||
mem_file = stringtf("%s/zm.mmap.%u", staticConfig.PATH_MAP.c_str(), id);
|
||||
#else // ZM_MEM_MAPPED
|
||||
shm_id = 0;
|
||||
#endif // ZM_MEM_MAPPED
|
||||
mem_size = 0;
|
||||
mem_ptr = nullptr;
|
||||
|
||||
last_event_id = 0;
|
||||
last_state = IDLE;
|
||||
|
||||
last_connect_time = 0;
|
||||
connected = false;
|
||||
}
|
||||
|
||||
Monitor::MonitorLink::~MonitorLink() {
|
||||
disconnect();
|
||||
}
|
||||
|
||||
bool Monitor::MonitorLink::connect() {
|
||||
SystemTimePoint now = std::chrono::system_clock::now();
|
||||
if (!last_connect_time || (now - std::chrono::system_clock::from_time_t(last_connect_time)) > Seconds(60)) {
|
||||
last_connect_time = std::chrono::system_clock::to_time_t(now);
|
||||
|
||||
mem_size = sizeof(SharedData) + sizeof(TriggerData);
|
||||
|
||||
Debug(1, "link.mem.size=%jd", static_cast<intmax_t>(mem_size));
|
||||
#if ZM_MEM_MAPPED
|
||||
map_fd = open(mem_file.c_str(), O_RDWR, (mode_t)0600);
|
||||
if (map_fd < 0) {
|
||||
Debug(3, "Can't open linked memory map file %s: %s", mem_file.c_str(), strerror(errno));
|
||||
disconnect();
|
||||
return false;
|
||||
}
|
||||
while (map_fd <= 2) {
|
||||
int new_map_fd = dup(map_fd);
|
||||
Warning("Got one of the stdio fds for our mmap handle. map_fd was %d, new one is %d", map_fd, new_map_fd);
|
||||
close(map_fd);
|
||||
map_fd = new_map_fd;
|
||||
}
|
||||
|
||||
struct stat map_stat;
|
||||
if (fstat(map_fd, &map_stat) < 0) {
|
||||
Error("Can't stat linked memory map file %s: %s", mem_file.c_str(), strerror(errno));
|
||||
disconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (map_stat.st_size == 0) {
|
||||
Error("Linked memory map file %s is empty: %s", mem_file.c_str(), strerror(errno));
|
||||
disconnect();
|
||||
return false;
|
||||
} else if (map_stat.st_size < mem_size) {
|
||||
Error("Got unexpected memory map file size %ld, expected %jd", map_stat.st_size, static_cast<intmax_t>(mem_size));
|
||||
disconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0);
|
||||
if (mem_ptr == MAP_FAILED) {
|
||||
Error("Can't map file %s (%jd bytes) to memory: %s", mem_file.c_str(), static_cast<intmax_t>(mem_size), strerror(errno));
|
||||
disconnect();
|
||||
return false;
|
||||
}
|
||||
#else // ZM_MEM_MAPPED
|
||||
shm_id = shmget((config.shm_key&0xffff0000)|id, mem_size, 0700);
|
||||
if (shm_id < 0) {
|
||||
Debug(3, "Can't shmget link memory: %s", strerror(errno));
|
||||
connected = false;
|
||||
return false;
|
||||
}
|
||||
mem_ptr = (unsigned char *)shmat(shm_id, 0, 0);
|
||||
if ((int)mem_ptr == -1) {
|
||||
Debug(3, "Can't shmat link memory: %s", strerror(errno));
|
||||
connected = false;
|
||||
return false;
|
||||
}
|
||||
#endif // ZM_MEM_MAPPED
|
||||
|
||||
shared_data = (SharedData *)mem_ptr;
|
||||
trigger_data = (TriggerData *)((char *)shared_data + sizeof(SharedData));
|
||||
|
||||
if (!shared_data->valid) {
|
||||
Debug(3, "Linked memory not initialised by capture daemon");
|
||||
disconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
last_state = shared_data->state;
|
||||
last_event_id = shared_data->last_event_id;
|
||||
connected = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} // end bool Monitor::MonitorLink::connect()
|
||||
|
||||
bool Monitor::MonitorLink::disconnect() {
|
||||
if (connected) {
|
||||
connected = false;
|
||||
|
||||
#if ZM_MEM_MAPPED
|
||||
if (mem_ptr > (void *)0) {
|
||||
msync(mem_ptr, mem_size, MS_ASYNC);
|
||||
munmap(mem_ptr, mem_size);
|
||||
}
|
||||
if (map_fd >= 0)
|
||||
close(map_fd);
|
||||
|
||||
map_fd = -1;
|
||||
#else // ZM_MEM_MAPPED
|
||||
struct shmid_ds shm_data;
|
||||
if (shmctl(shm_id, IPC_STAT, &shm_data) < 0) {
|
||||
Debug(3, "Can't shmctl: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
shm_id = 0;
|
||||
|
||||
if (shm_data.shm_nattch <= 1) {
|
||||
if (shmctl(shm_id, IPC_RMID, 0) < 0) {
|
||||
Debug(3, "Can't shmctl: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (shmdt(mem_ptr) < 0) {
|
||||
Debug(3, "Can't shmdt: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
#endif // ZM_MEM_MAPPED
|
||||
mem_size = 0;
|
||||
mem_ptr = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Monitor::MonitorLink::isAlarmed() {
|
||||
if (!connected) {
|
||||
return false;
|
||||
}
|
||||
return( shared_data->state == ALARM );
|
||||
}
|
||||
|
||||
bool Monitor::MonitorLink::inAlarm() {
|
||||
if (!connected) {
|
||||
return false;
|
||||
}
|
||||
return( shared_data->state == ALARM || shared_data->state == ALERT );
|
||||
}
|
||||
|
||||
bool Monitor::MonitorLink::hasAlarmed() {
|
||||
if (shared_data->state == ALARM) {
|
||||
return true;
|
||||
}
|
||||
last_event_id = shared_data->last_event_id;
|
||||
return false;
|
||||
}
|
||||
|
||||
Monitor::Monitor()
|
||||
: id(0),
|
||||
name(""),
|
||||
|
@ -434,7 +266,9 @@ Monitor::Monitor()
|
|||
privacy_bitmask(nullptr),
|
||||
n_linked_monitors(0),
|
||||
linked_monitors(nullptr),
|
||||
ONVIF_Closes_Event(FALSE),
|
||||
Event_Poller_Closes_Event(FALSE),
|
||||
Janus_Manager(nullptr),
|
||||
Amcrest_Manager(nullptr),
|
||||
#ifdef WITH_GSOAP
|
||||
soap(nullptr),
|
||||
#endif
|
||||
|
@ -1101,21 +935,16 @@ bool Monitor::connect() {
|
|||
|
||||
|
||||
//ONVIF and Amcrest Setup
|
||||
//since they serve the same function, handling them as two options of the same feature.
|
||||
ONVIF_Trigger_State = FALSE;
|
||||
//For now, only support one event type per camera, so share some state.
|
||||
Poll_Trigger_State = FALSE;
|
||||
if (onvif_event_listener) { //
|
||||
Debug(1, "Starting ONVIF");
|
||||
ONVIF_Healthy = FALSE;
|
||||
Event_Poller_Healthy = FALSE;
|
||||
if (onvif_options.find("closes_event") != std::string::npos) { //Option to indicate that ONVIF will send a close event message
|
||||
ONVIF_Closes_Event = TRUE;
|
||||
Event_Poller_Closes_Event = TRUE;
|
||||
}
|
||||
if (use_Amcrest_API) {
|
||||
curl_multi = curl_multi_init();
|
||||
start_Amcrest();
|
||||
//spin up curl_multi
|
||||
//use the onvif_user and onvif_pass and onvif_url here.
|
||||
//going to use the non-blocking curl api, and in the polling thread, block for 5 seconds waiting for input, just like onvif
|
||||
//note that it's not possible for a single camera to use both.
|
||||
Amcrest_Manager = new AmcrestAPI(this);
|
||||
} else { //using GSOAP
|
||||
#ifdef WITH_GSOAP
|
||||
tev__PullMessages.Timeout = "PT600S";
|
||||
|
@ -1141,7 +970,7 @@ bool Monitor::connect() {
|
|||
Error("Couldn't do initial event pull! Error %i %s, %s", soap->error, soap_fault_string(soap), soap_fault_detail(soap));
|
||||
} else {
|
||||
Debug(1, "Good Initial ONVIF Pull");
|
||||
ONVIF_Healthy = TRUE;
|
||||
Event_Poller_Healthy = TRUE;
|
||||
}
|
||||
}
|
||||
#else
|
||||
|
@ -1153,17 +982,9 @@ bool Monitor::connect() {
|
|||
}
|
||||
//End ONVIF Setup
|
||||
|
||||
#if HAVE_LIBCURL //janus setup. Depends on libcurl.
|
||||
if (janus_enabled && (path.find("rtsp://") != std::string::npos)) {
|
||||
get_janus_session();
|
||||
if (add_to_janus() != 0) {
|
||||
Warning("Failed to add monitor stream to Janus!"); //The first attempt may fail. Will be reattempted in the Poller thread
|
||||
if (janus_enabled) {
|
||||
Janus_Manager = new JanusManager(this);
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (janus_enabled)
|
||||
Error("zmc not compiled with LIBCURL. Janus support not built in!");
|
||||
#endif
|
||||
|
||||
} else if (!shared_data->valid) {
|
||||
Error("Shared data not initialised by capture daemon for monitor %s", name.c_str());
|
||||
|
@ -1260,12 +1081,12 @@ Monitor::~Monitor() {
|
|||
sws_freeContext(convert_context);
|
||||
convert_context = nullptr;
|
||||
}
|
||||
if (Amcrest_handle != nullptr) {
|
||||
curl_multi_remove_handle(curl_multi, Amcrest_handle);
|
||||
curl_easy_cleanup(Amcrest_handle);
|
||||
if (Amcrest_Manager != nullptr) {
|
||||
delete Amcrest_Manager;
|
||||
}
|
||||
if (purpose == CAPTURE) {
|
||||
curl_global_cleanup(); //not sure about this location.
|
||||
}
|
||||
if (curl_multi != nullptr) curl_multi_cleanup(curl_multi);
|
||||
curl_global_cleanup();
|
||||
} // end Monitor::~Monitor()
|
||||
|
||||
void Monitor::AddPrivacyBitmask() {
|
||||
|
@ -1834,38 +1655,13 @@ void Monitor::UpdateFPS() {
|
|||
//Thread where ONVIF polling, and other similar status polling can happen.
|
||||
//Since these can be blocking, run here to avoid intefering with other processing
|
||||
bool Monitor::Poll() {
|
||||
|
||||
//We want to trigger every 5 seconds or so. so grab the time at the beginning of the loop, and sleep at the end.
|
||||
std::chrono::system_clock::time_point loop_start_time = std::chrono::system_clock::now();
|
||||
|
||||
if (ONVIF_Healthy) {
|
||||
if (Event_Poller_Healthy) {
|
||||
if(use_Amcrest_API) {
|
||||
int open_handles;
|
||||
int transfers;
|
||||
curl_multi_perform(curl_multi, &open_handles);
|
||||
if (open_handles == 0) {
|
||||
start_Amcrest(); //http transfer ended, need to restart.
|
||||
} else {
|
||||
curl_multi_wait(curl_multi, NULL, 0, 5000, &transfers); //wait for max 5 seconds for event.
|
||||
if (transfers > 0) { //have data to deal with
|
||||
curl_multi_perform(curl_multi, &open_handles); //actually grabs the data, populates amcrest_response
|
||||
if (amcrest_response.find("action=Start") != std::string::npos) {
|
||||
//Event Start
|
||||
Debug(1,"Triggered on ONVIF");
|
||||
if (!ONVIF_Trigger_State) {
|
||||
Debug(1,"Triggered Event");
|
||||
ONVIF_Trigger_State = TRUE;
|
||||
}
|
||||
} else if (amcrest_response.find("action=Stop") != std::string::npos){
|
||||
Debug(1, "Triggered off ONVIF");
|
||||
ONVIF_Trigger_State = FALSE;
|
||||
if (!ONVIF_Closes_Event) { //If we get a close event, then we know to expect them.
|
||||
ONVIF_Closes_Event = TRUE;
|
||||
Debug(1,"Setting ClosesEvent");
|
||||
}
|
||||
}
|
||||
amcrest_response.clear(); //We've dealt with the message, need to clear the queue
|
||||
}
|
||||
}
|
||||
Amcrest_Manager->WaitForMessage();
|
||||
} else {
|
||||
|
||||
#ifdef WITH_GSOAP
|
||||
|
@ -1874,7 +1670,7 @@ bool Monitor::Poll() {
|
|||
if (result != SOAP_OK) {
|
||||
if (result != SOAP_EOF) { //Ignore the timeout error
|
||||
Error("Failed to get ONVIF messages! %s", soap_fault_string(soap));
|
||||
ONVIF_Healthy = FALSE;
|
||||
Event_Poller_Healthy = FALSE;
|
||||
}
|
||||
} else {
|
||||
Debug(1, "Got Good Response! %i", result);
|
||||
|
@ -1891,16 +1687,16 @@ bool Monitor::Poll() {
|
|||
if (strcmp(msg->Message.__any.elts->next->elts->atts->next->text, "true") == 0) {
|
||||
//Event Start
|
||||
Debug(1,"Triggered on ONVIF");
|
||||
if (!ONVIF_Trigger_State) {
|
||||
if (!Poll_Trigger_State) {
|
||||
Debug(1,"Triggered Event");
|
||||
ONVIF_Trigger_State = TRUE;
|
||||
Poll_Trigger_State = TRUE;
|
||||
std::this_thread::sleep_for (std::chrono::seconds(1)); //thread sleep
|
||||
}
|
||||
} else {
|
||||
Debug(1, "Triggered off ONVIF");
|
||||
ONVIF_Trigger_State = FALSE;
|
||||
if (!ONVIF_Closes_Event) { //If we get a close event, then we know to expect them.
|
||||
ONVIF_Closes_Event = TRUE;
|
||||
Poll_Trigger_State = FALSE;
|
||||
if (!Event_Poller_Closes_Event) { //If we get a close event, then we know to expect them.
|
||||
Event_Poller_Closes_Event = TRUE;
|
||||
Debug(1,"Setting ClosesEvent");
|
||||
}
|
||||
}
|
||||
|
@ -1911,11 +1707,9 @@ bool Monitor::Poll() {
|
|||
}
|
||||
}
|
||||
if (janus_enabled) {
|
||||
if (janus_session.empty()) {
|
||||
get_janus_session();
|
||||
}
|
||||
if (check_janus() == 0) {
|
||||
add_to_janus();
|
||||
|
||||
if (Janus_Manager->check_janus() == 0) {
|
||||
Janus_Manager->add_to_janus();
|
||||
}
|
||||
}
|
||||
std::this_thread::sleep_until(loop_start_time + std::chrono::seconds(5));
|
||||
|
@ -1970,8 +1764,8 @@ bool Monitor::Analyse() {
|
|||
Event::StringSetMap noteSetMap;
|
||||
|
||||
#ifdef WITH_GSOAP
|
||||
if (onvif_event_listener && ONVIF_Healthy) {
|
||||
if (ONVIF_Trigger_State) {
|
||||
if (onvif_event_listener && Event_Poller_Healthy) {
|
||||
if (Poll_Trigger_State) {
|
||||
score += 9;
|
||||
Debug(1, "Triggered on ONVIF");
|
||||
Event::StringSet noteSet;
|
||||
|
@ -1979,10 +1773,10 @@ bool Monitor::Analyse() {
|
|||
noteSetMap[MOTION_CAUSE] = noteSet;
|
||||
cause += "ONVIF";
|
||||
//If the camera isn't going to send an event close, we need to close it here, but only after it has actually triggered an alarm.
|
||||
if (!ONVIF_Closes_Event && state == ALARM)
|
||||
ONVIF_Trigger_State = FALSE;
|
||||
if (!Event_Poller_Closes_Event && state == ALARM)
|
||||
Poll_Trigger_State = FALSE;
|
||||
} // end ONVIF_Trigger
|
||||
} // end if (onvif_event_listener && ONVIF_Healthy)
|
||||
} // end if (onvif_event_listener && Event_Poller_Healthy)
|
||||
#endif
|
||||
|
||||
// Specifically told to be on. Setting the score here is not enough to trigger the alarm. Must jump directly to ALARM
|
||||
|
@ -3292,8 +3086,7 @@ int Monitor::PrimeCapture() {
|
|||
} // end if rtsp_server
|
||||
|
||||
//Poller Thread
|
||||
if (onvif_event_listener || janus_enabled) {
|
||||
|
||||
if (onvif_event_listener || janus_enabled || use_Amcrest_API) {
|
||||
if (!Poller) {
|
||||
Poller = zm::make_unique<PollThread>(this);
|
||||
} else {
|
||||
|
@ -3343,10 +3136,6 @@ int Monitor::Close() {
|
|||
if (Poller) {
|
||||
Poller->Stop();
|
||||
}
|
||||
if (curl_multi != nullptr) {
|
||||
curl_multi_cleanup(curl_multi);
|
||||
curl_multi = nullptr;
|
||||
}
|
||||
#ifdef WITH_GSOAP
|
||||
if (onvif_event_listener && (soap != nullptr)) {
|
||||
Debug(1, "Tearing Down Onvif");
|
||||
|
@ -3359,11 +3148,11 @@ int Monitor::Close() {
|
|||
soap = nullptr;
|
||||
} //End ONVIF
|
||||
#endif
|
||||
#if HAVE_LIBCURL //Janus Teardown
|
||||
//Janus Teardown
|
||||
if (janus_enabled && (purpose == CAPTURE)) {
|
||||
remove_from_janus();
|
||||
delete Janus_Manager;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
packetqueue.clear();
|
||||
if (audio_fifo) {
|
||||
|
@ -3500,289 +3289,3 @@ int SOAP_ENV__Fault(struct soap *soap, char *faultcode, char *faultstring, char
|
|||
return soap_send_empty_response(soap, SOAP_OK);
|
||||
}
|
||||
#endif
|
||||
|
||||
size_t Monitor::WriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
|
||||
{
|
||||
((std::string*)userp)->append((char*)contents, size * nmemb);
|
||||
return size * nmemb;
|
||||
}
|
||||
|
||||
int Monitor::add_to_janus() {
|
||||
std::string response;
|
||||
std::string endpoint;
|
||||
if ((config.janus_path != nullptr) && (config.janus_path[0] != '\0')) {
|
||||
endpoint = config.janus_path;
|
||||
} else {
|
||||
endpoint = "127.0.0.1:8088/janus/";
|
||||
}
|
||||
std::string postData = "{\"janus\" : \"create\", \"transaction\" : \"randomString\"}";
|
||||
std::string rtsp_username;
|
||||
std::string rtsp_password;
|
||||
std::string rtsp_path = "rtsp://";
|
||||
std::size_t pos;
|
||||
std::size_t pos2;
|
||||
CURLcode res;
|
||||
|
||||
curl = curl_easy_init();
|
||||
if (!curl) {
|
||||
Error("Failed to init curl");
|
||||
return -1;
|
||||
}
|
||||
//parse username and password
|
||||
pos = path.find(":", 7);
|
||||
if (pos == std::string::npos) return -1;
|
||||
rtsp_username = path.substr(7, pos-7);
|
||||
|
||||
pos2 = path.find("@", pos);
|
||||
if (pos2 == std::string::npos) return -1;
|
||||
|
||||
rtsp_password = path.substr(pos+1, pos2 - pos - 1);
|
||||
rtsp_path += path.substr(pos2 + 1);
|
||||
|
||||
endpoint += "/";
|
||||
endpoint += janus_session;
|
||||
|
||||
//Assemble our actual request
|
||||
postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {";
|
||||
postData += "\"request\" : \"create\", \"admin_key\" : \"";
|
||||
postData += config.janus_secret;
|
||||
postData += "\", \"type\" : \"rtsp\", ";
|
||||
postData += "\"url\" : \"";
|
||||
postData += rtsp_path;
|
||||
postData += "\", \"rtsp_user\" : \"";
|
||||
postData += rtsp_username;
|
||||
postData += "\", \"rtsp_pwd\" : \"";
|
||||
postData += rtsp_password;
|
||||
postData += "\", \"id\" : ";
|
||||
postData += std::to_string(id);
|
||||
if (janus_audio_enabled) postData += ", \"audio\" : true";
|
||||
postData += ", \"video\" : true}}";
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str());
|
||||
res = curl_easy_perform(curl);
|
||||
if (res != CURLE_OK) {
|
||||
Error("Failed to curl_easy_perform adding rtsp stream");
|
||||
curl_easy_cleanup(curl);
|
||||
return -1;
|
||||
}
|
||||
if ((response.find("error") != std::string::npos) && ((response.find("No such session") != std::string::npos) || (response.find("No such handle") != std::string::npos))) {
|
||||
janus_session = "";
|
||||
curl_easy_cleanup(curl);
|
||||
return -2;
|
||||
}
|
||||
//scan for missing session or handle id "No such session" "no such handle"
|
||||
|
||||
Debug(1,"Added stream to Janus: %s", response.c_str());
|
||||
curl_easy_cleanup(curl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Monitor::check_janus() {
|
||||
std::string response;
|
||||
std::string endpoint;
|
||||
if ((config.janus_path != nullptr) && (config.janus_path[0] != '\0')) {
|
||||
endpoint = config.janus_path;
|
||||
} else {
|
||||
endpoint = "127.0.0.1:8088/janus/";
|
||||
}
|
||||
std::string postData;
|
||||
//std::size_t pos;
|
||||
CURLcode res;
|
||||
|
||||
curl = curl_easy_init();
|
||||
if(!curl) return -1;
|
||||
|
||||
endpoint += "/";
|
||||
endpoint += janus_session;
|
||||
|
||||
//Assemble our actual request
|
||||
postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {";
|
||||
postData += "\"request\" : \"info\", \"id\" : ";
|
||||
postData += std::to_string(id);
|
||||
postData += "}}";
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str());
|
||||
res = curl_easy_perform(curl);
|
||||
if (res != CURLE_OK) { //may mean an error code thrown by Janus, because of a bad session
|
||||
Warning("Attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res));
|
||||
curl_easy_cleanup(curl);
|
||||
janus_session = "";
|
||||
return -1;
|
||||
}
|
||||
|
||||
curl_easy_cleanup(curl);
|
||||
Debug(1, "Queried for stream status: %s", response.c_str());
|
||||
if ((response.find("error") != std::string::npos) && ((response.find("No such session") != std::string::npos) || (response.find("No such handle") != std::string::npos))) {
|
||||
Warning("Janus Session timed out");
|
||||
janus_session = "";
|
||||
return -2;
|
||||
} else if (response.find("No such mountpoint") != std::string::npos) {
|
||||
Warning("Mountpoint Missing");
|
||||
return 0;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int Monitor::remove_from_janus() {
|
||||
std::string response;
|
||||
std::string endpoint;
|
||||
if ((config.janus_path != nullptr) && (config.janus_path[0] != '\0')) {
|
||||
endpoint = config.janus_path;
|
||||
} else {
|
||||
endpoint = "127.0.0.1:8088/janus/";
|
||||
}
|
||||
std::string postData = "{\"janus\" : \"create\", \"transaction\" : \"randomString\"}";
|
||||
//std::size_t pos;
|
||||
CURLcode res;
|
||||
|
||||
curl = curl_easy_init();
|
||||
if(!curl) return -1;
|
||||
|
||||
endpoint += "/";
|
||||
endpoint += janus_session;
|
||||
|
||||
//Assemble our actual request
|
||||
postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {";
|
||||
postData += "\"request\" : \"destroy\", \"admin_key\" : \"";
|
||||
postData += config.janus_secret;
|
||||
postData += "\", \"id\" : ";
|
||||
postData += std::to_string(id);
|
||||
postData += "}}";
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str());
|
||||
res = curl_easy_perform(curl);
|
||||
if (res != CURLE_OK) {
|
||||
Warning("Libcurl attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res));
|
||||
curl_easy_cleanup(curl);
|
||||
return -1;
|
||||
}
|
||||
|
||||
Debug(1, "Removed stream from Janus: %s", response.c_str());
|
||||
curl_easy_cleanup(curl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Monitor::get_janus_session() {
|
||||
std::string response;
|
||||
std::string endpoint;
|
||||
if ((config.janus_path != nullptr) && (config.janus_path[0] != '\0')) {
|
||||
endpoint = config.janus_path;
|
||||
} else {
|
||||
endpoint = "127.0.0.1:8088/janus/";
|
||||
}
|
||||
std::string postData = "{\"janus\" : \"create\", \"transaction\" : \"randomString\"}";
|
||||
std::size_t pos;
|
||||
CURLcode res;
|
||||
curl = curl_easy_init();
|
||||
if(!curl) return -1;
|
||||
|
||||
//Start Janus API init. Need to get a session_id and handle_id
|
||||
curl_easy_setopt(curl, CURLOPT_URL, endpoint.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str());
|
||||
res = curl_easy_perform(curl);
|
||||
if (res != CURLE_OK) {
|
||||
Warning("Libcurl attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res));
|
||||
curl_easy_cleanup(curl);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pos = response.find("\"id\": ");
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
curl_easy_cleanup(curl);
|
||||
return -1;
|
||||
}
|
||||
janus_session = response.substr(pos + 6, 16);
|
||||
|
||||
response = "";
|
||||
endpoint += "/";
|
||||
endpoint += janus_session;
|
||||
postData = "{\"janus\" : \"attach\", \"plugin\" : \"janus.plugin.streaming\", \"transaction\" : \"randomString\"}";
|
||||
curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str());
|
||||
res = curl_easy_perform(curl);
|
||||
if (res != CURLE_OK)
|
||||
{
|
||||
Warning("Libcurl attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res));
|
||||
curl_easy_cleanup(curl);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pos = response.find("\"id\": ");
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
curl_easy_cleanup(curl);
|
||||
return -1;
|
||||
}
|
||||
janus_session += "/";
|
||||
janus_session += response.substr(pos + 6, 16);
|
||||
curl_easy_cleanup(curl);
|
||||
return 1;
|
||||
} //get_janus_session
|
||||
|
||||
int Monitor::start_Amcrest() {
|
||||
//init the transfer and start it in multi-handle
|
||||
int running_handles;
|
||||
long response_code;
|
||||
struct CURLMsg *m;
|
||||
CURLMcode curl_error;
|
||||
if (Amcrest_handle != nullptr) { //potentially clean up the old handle
|
||||
curl_multi_remove_handle(curl_multi, Amcrest_handle);
|
||||
curl_easy_cleanup(Amcrest_handle);
|
||||
}
|
||||
|
||||
std::string full_url = onvif_url;
|
||||
if (full_url.back() != '/') full_url += '/';
|
||||
full_url += "eventManager.cgi?action=attach&codes=[VideoMotion]";
|
||||
Amcrest_handle = curl_easy_init();
|
||||
if (!Amcrest_handle){
|
||||
Warning("Handle is null!");
|
||||
return -1;
|
||||
}
|
||||
curl_easy_setopt(Amcrest_handle, CURLOPT_URL, full_url.c_str());
|
||||
curl_easy_setopt(Amcrest_handle, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||
curl_easy_setopt(Amcrest_handle, CURLOPT_WRITEDATA, &amcrest_response);
|
||||
curl_easy_setopt(Amcrest_handle, CURLOPT_USERNAME, onvif_username.c_str());
|
||||
curl_easy_setopt(Amcrest_handle, CURLOPT_PASSWORD, onvif_password.c_str());
|
||||
curl_easy_setopt(Amcrest_handle, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
|
||||
curl_error = curl_multi_add_handle(curl_multi, Amcrest_handle);
|
||||
Warning("error of %i", curl_error);
|
||||
curl_error = curl_multi_perform(curl_multi, &running_handles);
|
||||
if (curl_error == CURLM_OK) {
|
||||
curl_easy_getinfo(Amcrest_handle, CURLINFO_RESPONSE_CODE, &response_code);
|
||||
int msgq = 0;
|
||||
m = curl_multi_info_read(curl_multi, &msgq);
|
||||
if (m && (m->msg == CURLMSG_DONE)) {
|
||||
Warning("Libcurl exited Early: %i", m->data.result);
|
||||
}
|
||||
|
||||
curl_multi_wait(curl_multi, NULL, 0, 300, NULL);
|
||||
curl_error = curl_multi_perform(curl_multi, &running_handles);
|
||||
}
|
||||
|
||||
if ((curl_error == CURLM_OK) && (running_handles > 0)) {
|
||||
ONVIF_Healthy = TRUE;
|
||||
} else {
|
||||
Warning("Response: %s", amcrest_response.c_str());
|
||||
Warning("Seeing %i streams, and error of %i, url: %s", running_handles, curl_error, full_url.c_str());
|
||||
curl_easy_getinfo(Amcrest_handle, CURLINFO_OS_ERRNO, &response_code);
|
||||
Warning("Response code: %lu", response_code);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -290,7 +290,49 @@ protected:
|
|||
bool hasAlarmed();
|
||||
};
|
||||
|
||||
class AmcrestAPI {
|
||||
protected:
|
||||
Monitor *parent;
|
||||
std::string amcrest_response;
|
||||
CURLM *curl_multi = nullptr;
|
||||
CURL *Amcrest_handle = nullptr;
|
||||
static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp);
|
||||
|
||||
public:
|
||||
AmcrestAPI( Monitor *parent_);
|
||||
~AmcrestAPI();
|
||||
int API_Connect();
|
||||
void WaitForMessage();
|
||||
bool Amcrest_Alarmed;
|
||||
int start_Amcrest();
|
||||
};
|
||||
|
||||
class JanusManager {
|
||||
protected:
|
||||
Monitor *parent;
|
||||
CURL *curl = nullptr;
|
||||
//helper class for CURL
|
||||
static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp);
|
||||
bool Janus_Healthy;
|
||||
std::string janus_session;
|
||||
std::string janus_handle;
|
||||
std::string janus_endpoint;
|
||||
std::string stream_key;
|
||||
std::string rtsp_username;
|
||||
std::string rtsp_password;
|
||||
std::string rtsp_path;
|
||||
|
||||
public:
|
||||
JanusManager(Monitor *parent_);
|
||||
~JanusManager();
|
||||
int add_to_janus();
|
||||
int check_janus();
|
||||
int remove_from_janus();
|
||||
int get_janus_session();
|
||||
int get_janus_handle();
|
||||
int get_janus_plugin();
|
||||
std::string get_stream_key();
|
||||
};
|
||||
|
||||
|
||||
// These are read from the DB and thereafter remain unchanged
|
||||
|
@ -324,7 +366,6 @@ protected:
|
|||
std::string onvif_username;
|
||||
std::string onvif_password;
|
||||
std::string onvif_options;
|
||||
std::string amcrest_response;
|
||||
bool onvif_event_listener;
|
||||
bool use_Amcrest_API;
|
||||
|
||||
|
@ -478,9 +519,12 @@ protected:
|
|||
std::string diag_path_delta;
|
||||
|
||||
//ONVIF
|
||||
bool ONVIF_Trigger_State; //Re-using some variables for Amcrest API support
|
||||
bool ONVIF_Healthy;
|
||||
bool ONVIF_Closes_Event;
|
||||
bool Poll_Trigger_State;
|
||||
bool Event_Poller_Healthy;
|
||||
bool Event_Poller_Closes_Event;
|
||||
|
||||
JanusManager *Janus_Manager;
|
||||
AmcrestAPI *Amcrest_Manager;
|
||||
|
||||
#ifdef WITH_GSOAP
|
||||
struct soap *soap = nullptr;
|
||||
|
@ -492,17 +536,6 @@ protected:
|
|||
void set_credentials(struct soap *soap);
|
||||
#endif
|
||||
|
||||
//curl stuff
|
||||
CURL *curl = nullptr;
|
||||
CURLM *curl_multi = nullptr;
|
||||
CURL *Amcrest_handle = nullptr;
|
||||
int start_Amcrest();
|
||||
//helper class for CURL
|
||||
static size_t WriteCallback(void *contents, size_t size, size_t nmemb, void *userp);
|
||||
int add_to_janus();
|
||||
int remove_from_janus();
|
||||
int get_janus_session();
|
||||
std::string janus_session;
|
||||
|
||||
// Used in check signal
|
||||
uint8_t red_val;
|
||||
|
@ -573,11 +606,9 @@ public:
|
|||
return onvif_event_listener;
|
||||
}
|
||||
int check_janus(); //returns 1 for healthy, 0 for success but missing stream, negative for error.
|
||||
#ifdef WITH_GSOAP
|
||||
bool OnvifHealthy() {
|
||||
return ONVIF_Healthy;
|
||||
bool EventPollerHealthy() {
|
||||
return Event_Poller_Healthy;
|
||||
}
|
||||
#endif
|
||||
inline const char *EventPrefix() const { return event_prefix.c_str(); }
|
||||
inline bool Ready() const {
|
||||
if (image_count >= ready_count) {
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
//
|
||||
// ZoneMinder Monitor::AmcrestAPI Class Implementation, $Date$, $Revision$
|
||||
// Copyright (C) 2022 Jonathan Bennett
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
//
|
||||
|
||||
#include "zm_monitor.h"
|
||||
|
||||
|
||||
Monitor::AmcrestAPI::AmcrestAPI(Monitor *parent_) {
|
||||
parent = parent_;
|
||||
curl_multi = curl_multi_init();
|
||||
start_Amcrest();
|
||||
}
|
||||
|
||||
Monitor::AmcrestAPI::~AmcrestAPI() {
|
||||
if (Amcrest_handle != nullptr) { //potentially clean up the old handle
|
||||
curl_multi_remove_handle(curl_multi, Amcrest_handle);
|
||||
curl_easy_cleanup(Amcrest_handle);
|
||||
}
|
||||
if (curl_multi != nullptr) curl_multi_cleanup(curl_multi);
|
||||
}
|
||||
|
||||
int Monitor::AmcrestAPI::start_Amcrest() {
|
||||
//init the transfer and start it in multi-handle
|
||||
int running_handles;
|
||||
long response_code;
|
||||
struct CURLMsg *m;
|
||||
CURLMcode curl_error;
|
||||
if (Amcrest_handle != nullptr) { //potentially clean up the old handle
|
||||
curl_multi_remove_handle(curl_multi, Amcrest_handle);
|
||||
curl_easy_cleanup(Amcrest_handle);
|
||||
}
|
||||
|
||||
std::string full_url = parent->onvif_url;
|
||||
if (full_url.back() != '/') full_url += '/';
|
||||
full_url += "eventManager.cgi?action=attach&codes=[VideoMotion]";
|
||||
Amcrest_handle = curl_easy_init();
|
||||
if (!Amcrest_handle){
|
||||
Warning("Handle is null!");
|
||||
return -1;
|
||||
}
|
||||
curl_easy_setopt(Amcrest_handle, CURLOPT_URL, full_url.c_str());
|
||||
curl_easy_setopt(Amcrest_handle, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||
curl_easy_setopt(Amcrest_handle, CURLOPT_WRITEDATA, &amcrest_response);
|
||||
curl_easy_setopt(Amcrest_handle, CURLOPT_USERNAME, parent->onvif_username.c_str());
|
||||
curl_easy_setopt(Amcrest_handle, CURLOPT_PASSWORD, parent->onvif_password.c_str());
|
||||
curl_easy_setopt(Amcrest_handle, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
|
||||
curl_error = curl_multi_add_handle(curl_multi, Amcrest_handle);
|
||||
if (curl_error != CURLM_OK) {
|
||||
Warning("error of %i", curl_error);
|
||||
}
|
||||
curl_error = curl_multi_perform(curl_multi, &running_handles);
|
||||
if (curl_error == CURLM_OK) {
|
||||
curl_easy_getinfo(Amcrest_handle, CURLINFO_RESPONSE_CODE, &response_code);
|
||||
int msgq = 0;
|
||||
m = curl_multi_info_read(curl_multi, &msgq);
|
||||
if (m && (m->msg == CURLMSG_DONE)) {
|
||||
Warning("Libcurl exited Early: %i", m->data.result);
|
||||
}
|
||||
|
||||
curl_multi_wait(curl_multi, NULL, 0, 300, NULL);
|
||||
curl_error = curl_multi_perform(curl_multi, &running_handles);
|
||||
}
|
||||
|
||||
if ((curl_error == CURLM_OK) && (running_handles > 0)) {
|
||||
parent->Event_Poller_Healthy = TRUE;
|
||||
} else {
|
||||
Warning("Response: %s", amcrest_response.c_str());
|
||||
Warning("Seeing %i streams, and error of %i, url: %s", running_handles, curl_error, full_url.c_str());
|
||||
curl_easy_getinfo(Amcrest_handle, CURLINFO_OS_ERRNO, &response_code);
|
||||
Warning("Response code: %lu", response_code);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Monitor::AmcrestAPI::WaitForMessage() {
|
||||
int open_handles;
|
||||
int transfers;
|
||||
curl_multi_perform(curl_multi, &open_handles);
|
||||
if (open_handles == 0) {
|
||||
start_Amcrest(); //http transfer ended, need to restart.
|
||||
} else {
|
||||
curl_multi_wait(curl_multi, NULL, 0, 5000, &transfers); //wait for max 5 seconds for event.
|
||||
if (transfers > 0) { //have data to deal with
|
||||
curl_multi_perform(curl_multi, &open_handles); //actually grabs the data, populates amcrest_response
|
||||
if (amcrest_response.find("action=Start") != std::string::npos) {
|
||||
//Event Start
|
||||
Debug(1,"Triggered on ONVIF");
|
||||
if (!parent->Poll_Trigger_State) {
|
||||
Debug(1,"Triggered Event");
|
||||
parent->Poll_Trigger_State = TRUE;
|
||||
}
|
||||
} else if (amcrest_response.find("action=Stop") != std::string::npos){
|
||||
Debug(1, "Triggered off ONVIF");
|
||||
parent->Poll_Trigger_State = FALSE;
|
||||
if (!parent->Event_Poller_Closes_Event) { //If we get a close event, then we know to expect them.
|
||||
parent->Event_Poller_Closes_Event = TRUE;
|
||||
Debug(1,"Setting ClosesEvent");
|
||||
}
|
||||
}
|
||||
amcrest_response.clear(); //We've dealt with the message, need to clear the queue
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
size_t Monitor::AmcrestAPI::WriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
|
||||
{
|
||||
((std::string*)userp)->append((char*)contents, size * nmemb);
|
||||
return size * nmemb;
|
||||
}
|
|
@ -0,0 +1,316 @@
|
|||
//
|
||||
// ZoneMinder Monitor::JanusManager Class Implementation, $Date$, $Revision$
|
||||
// Copyright (C) 2022 Jonathan Bennett
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
//
|
||||
|
||||
#include "zm_monitor.h"
|
||||
|
||||
|
||||
Monitor::JanusManager::JanusManager(Monitor *parent_) { //constructor takes care of init and calls add_to
|
||||
std::string response;
|
||||
std::size_t pos;
|
||||
parent = parent_;
|
||||
if ((config.janus_path != nullptr) && (config.janus_path[0] != '\0')) {
|
||||
janus_endpoint = config.janus_path; //TODO: strip trailing /
|
||||
} else {
|
||||
janus_endpoint = "127.0.0.1:8088/janus";
|
||||
}
|
||||
if (janus_endpoint.back() == '/') janus_endpoint.pop_back(); //remove the trailing slash if present
|
||||
std::size_t pos2 = parent->path.find("@", pos);
|
||||
if (pos2 != std::string::npos) { //If we find an @ symbol, we have a username/password. Otherwise, passwordless login.
|
||||
|
||||
std::size_t pos = parent->path.find(":", 7); //Search for the colon, but only after the RTSP:// text.
|
||||
if (pos == std::string::npos) throw std::runtime_error("Cannot Parse URL for Janus."); //Looks like an invalid url
|
||||
rtsp_username = parent->path.substr(7, pos-7);
|
||||
|
||||
rtsp_password = parent->path.substr(pos+1, pos2 - pos - 1);
|
||||
rtsp_path = "RTSP://";
|
||||
rtsp_path += parent->path.substr(pos2 + 1);
|
||||
|
||||
} else {
|
||||
rtsp_username = "";
|
||||
rtsp_password = "";
|
||||
rtsp_path = parent->path;
|
||||
}
|
||||
}
|
||||
|
||||
Monitor::JanusManager::~JanusManager() {
|
||||
if (janus_session.empty()) get_janus_session();
|
||||
if (janus_handle.empty()) get_janus_handle();
|
||||
|
||||
std::string response;
|
||||
std::string endpoint;
|
||||
|
||||
std::string postData = "{\"janus\" : \"create\", \"transaction\" : \"randomString\"}";
|
||||
//std::size_t pos;
|
||||
CURLcode res;
|
||||
|
||||
curl = curl_easy_init();
|
||||
if(!curl) return;
|
||||
|
||||
endpoint = janus_endpoint;
|
||||
endpoint += "/";
|
||||
endpoint += janus_session;
|
||||
endpoint += "/";
|
||||
endpoint += janus_handle;
|
||||
|
||||
//Assemble our actual request
|
||||
postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {";
|
||||
postData += "\"request\" : \"destroy\", \"admin_key\" : \"";
|
||||
postData += config.janus_secret;
|
||||
postData += "\", \"id\" : ";
|
||||
postData += std::to_string(parent->id);
|
||||
postData += "}}";
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str());
|
||||
res = curl_easy_perform(curl);
|
||||
if (res != CURLE_OK) {
|
||||
Warning("Libcurl attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res));
|
||||
curl_easy_cleanup(curl);
|
||||
return;
|
||||
}
|
||||
|
||||
Debug(1, "Removed stream from Janus: %s", response.c_str());
|
||||
curl_easy_cleanup(curl);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
|
||||
int Monitor::JanusManager::check_janus() {
|
||||
if (janus_session.empty()) get_janus_session();
|
||||
if (janus_handle.empty()) get_janus_handle();
|
||||
|
||||
std::string response;
|
||||
std::string endpoint = janus_endpoint;
|
||||
std::string postData;
|
||||
//std::size_t pos;
|
||||
CURLcode res;
|
||||
|
||||
curl = curl_easy_init();
|
||||
if(!curl) return -1;
|
||||
|
||||
endpoint += "/";
|
||||
endpoint += janus_session;
|
||||
endpoint += "/";
|
||||
endpoint += janus_handle;
|
||||
|
||||
//Assemble our actual request
|
||||
postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {";
|
||||
postData += "\"request\" : \"info\", \"id\" : ";
|
||||
postData += std::to_string(parent->id);
|
||||
postData += "}}";
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str());
|
||||
res = curl_easy_perform(curl);
|
||||
if (res != CURLE_OK) { //may mean an error code thrown by Janus, because of a bad session
|
||||
Warning("Attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res));
|
||||
curl_easy_cleanup(curl);
|
||||
janus_session = "";
|
||||
janus_handle = "";
|
||||
return -1;
|
||||
}
|
||||
|
||||
curl_easy_cleanup(curl);
|
||||
Debug(1, "Queried for stream status: %s", response.c_str());
|
||||
if (response.find("\"janus\": \"error\"") != std::string::npos) {
|
||||
if (response.find("No such session") != std::string::npos) {
|
||||
Warning("Janus Session timed out");
|
||||
janus_session = "";
|
||||
return -2;
|
||||
} else if (response.find("No such handle") != std::string::npos) {
|
||||
Warning("Janus Handle timed out");
|
||||
janus_handle = "";
|
||||
return -2;
|
||||
}
|
||||
} else if (response.find("No such mountpoint") != std::string::npos) {
|
||||
Warning("Mountpoint Missing");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int Monitor::JanusManager::add_to_janus() {
|
||||
if (janus_session.empty()) get_janus_session();
|
||||
if (janus_handle.empty()) get_janus_handle();
|
||||
|
||||
std::string response;
|
||||
std::string endpoint = janus_endpoint;
|
||||
|
||||
CURLcode res;
|
||||
|
||||
curl = curl_easy_init();
|
||||
if (!curl) {
|
||||
Error("Failed to init curl");
|
||||
return -1;
|
||||
}
|
||||
|
||||
endpoint += "/";
|
||||
endpoint += janus_session;
|
||||
endpoint += "/";
|
||||
endpoint += janus_handle;
|
||||
|
||||
//Assemble our actual request
|
||||
std::string postData = "{\"janus\" : \"message\", \"transaction\" : \"randomString\", \"body\" : {";
|
||||
postData += "\"request\" : \"create\", \"admin_key\" : \"";
|
||||
postData += config.janus_secret;
|
||||
postData += "\", \"type\" : \"rtsp\", ";
|
||||
postData += "\"url\" : \"";
|
||||
postData += rtsp_path;
|
||||
if (rtsp_username != "") {
|
||||
postData += "\", \"rtsp_user\" : \"";
|
||||
postData += rtsp_username;
|
||||
postData += "\", \"rtsp_pwd\" : \"";
|
||||
postData += rtsp_password;
|
||||
}
|
||||
postData += "\", \"id\" : ";
|
||||
postData += std::to_string(parent->id);
|
||||
if (parent->janus_audio_enabled) postData += ", \"audio\" : true";
|
||||
postData += ", \"video\" : true}}";
|
||||
Warning("Sending %s to %s", postData.c_str(), endpoint.c_str());
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str());
|
||||
res = curl_easy_perform(curl);
|
||||
if (res != CURLE_OK) {
|
||||
Error("Failed to curl_easy_perform adding rtsp stream");
|
||||
curl_easy_cleanup(curl);
|
||||
return -1;
|
||||
}
|
||||
if (response.find("\"janus\": \"error\"") != std::string::npos) {
|
||||
if (response.find("No such session") != std::string::npos) {
|
||||
Warning("Janus Session timed out");
|
||||
janus_session = "";
|
||||
return -2;
|
||||
} else if (response.find("No such handle") != std::string::npos) {
|
||||
Warning("Janus Handle timed out");
|
||||
janus_handle = "";
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
//scan for missing session or handle id "No such session" "no such handle"
|
||||
|
||||
Debug(1,"Added stream to Janus: %s", response.c_str());
|
||||
curl_easy_cleanup(curl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
size_t Monitor::JanusManager::WriteCallback(void *contents, size_t size, size_t nmemb, void *userp)
|
||||
{
|
||||
((std::string*)userp)->append((char*)contents, size * nmemb);
|
||||
return size * nmemb;
|
||||
}
|
||||
|
||||
/*
|
||||
void Monitor::JanusManager::generateKey()
|
||||
{
|
||||
const std::string CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
std::random_device random_device;
|
||||
std::mt19937 generator(random_device());
|
||||
std::uniform_int_distribution<> distribution(0, CHARACTERS.size() - 1);
|
||||
|
||||
std::string random_string;
|
||||
|
||||
for (std::size_t i = 0; i < 16; ++i)
|
||||
{
|
||||
random_string += CHARACTERS[distribution(generator)];
|
||||
}
|
||||
|
||||
stream_key = random_string;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
int Monitor::JanusManager::get_janus_session() {
|
||||
janus_session = "";
|
||||
std::string endpoint = janus_endpoint;
|
||||
|
||||
std::string response;
|
||||
|
||||
std::string postData = "{\"janus\" : \"create\", \"transaction\" : \"randomString\"}";
|
||||
std::size_t pos;
|
||||
CURLcode res;
|
||||
curl = curl_easy_init();
|
||||
if(!curl) return -1;
|
||||
|
||||
curl_easy_setopt(curl, CURLOPT_URL, endpoint.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str());
|
||||
res = curl_easy_perform(curl);
|
||||
if (res != CURLE_OK) {
|
||||
Warning("Libcurl attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res));
|
||||
curl_easy_cleanup(curl);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pos = response.find("\"id\": ");
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
curl_easy_cleanup(curl);
|
||||
return -1;
|
||||
}
|
||||
janus_session = response.substr(pos + 6, 16);
|
||||
curl_easy_cleanup(curl);
|
||||
return 1;
|
||||
|
||||
} //get_janus_session
|
||||
|
||||
int Monitor::JanusManager::get_janus_handle() {
|
||||
std::string response = "";
|
||||
std::string endpoint = janus_endpoint;
|
||||
std::size_t pos;
|
||||
|
||||
CURLcode res;
|
||||
curl = curl_easy_init();
|
||||
if(!curl) return -1;
|
||||
|
||||
endpoint += "/";
|
||||
endpoint += janus_session;
|
||||
std::string postData = "{\"janus\" : \"attach\", \"plugin\" : \"janus.plugin.streaming\", \"transaction\" : \"randomString\"}";
|
||||
curl_easy_setopt(curl, CURLOPT_URL,endpoint.c_str());
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response);
|
||||
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postData.c_str());
|
||||
res = curl_easy_perform(curl);
|
||||
if (res != CURLE_OK)
|
||||
{
|
||||
Warning("Libcurl attempted %s got %s", endpoint.c_str(), curl_easy_strerror(res));
|
||||
curl_easy_cleanup(curl);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pos = response.find("\"id\": ");
|
||||
if (pos == std::string::npos)
|
||||
{
|
||||
curl_easy_cleanup(curl);
|
||||
return -1;
|
||||
}
|
||||
janus_handle = response.substr(pos + 6, 16);
|
||||
curl_easy_cleanup(curl);
|
||||
return 1;
|
||||
} //get_janus_handle
|
|
@ -0,0 +1,198 @@
|
|||
//
|
||||
// ZoneMinder Monitor Class Implementation, $Date$, $Revision$
|
||||
// Copyright (C) 2001-2008 Philip Coombes
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
//
|
||||
|
||||
#include "zm_monitor.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
#if ZM_MEM_MAPPED
|
||||
#include <sys/mman.h>
|
||||
#include <fcntl.h>
|
||||
#else // ZM_MEM_MAPPED
|
||||
#include <sys/ipc.h>
|
||||
#include <sys/shm.h>
|
||||
#endif // ZM_MEM_MAPPED
|
||||
|
||||
Monitor::MonitorLink::MonitorLink(unsigned int p_id, const char *p_name) :
|
||||
id(p_id),
|
||||
shared_data(nullptr),
|
||||
trigger_data(nullptr),
|
||||
video_store_data(nullptr)
|
||||
{
|
||||
strncpy(name, p_name, sizeof(name)-1);
|
||||
|
||||
#if ZM_MEM_MAPPED
|
||||
map_fd = -1;
|
||||
mem_file = stringtf("%s/zm.mmap.%u", staticConfig.PATH_MAP.c_str(), id);
|
||||
#else // ZM_MEM_MAPPED
|
||||
shm_id = 0;
|
||||
#endif // ZM_MEM_MAPPED
|
||||
mem_size = 0;
|
||||
mem_ptr = nullptr;
|
||||
|
||||
last_event_id = 0;
|
||||
last_state = IDLE;
|
||||
|
||||
last_connect_time = 0;
|
||||
connected = false;
|
||||
}
|
||||
|
||||
Monitor::MonitorLink::~MonitorLink() {
|
||||
disconnect();
|
||||
}
|
||||
|
||||
bool Monitor::MonitorLink::connect() {
|
||||
SystemTimePoint now = std::chrono::system_clock::now();
|
||||
if (!last_connect_time || (now - std::chrono::system_clock::from_time_t(last_connect_time)) > Seconds(60)) {
|
||||
last_connect_time = std::chrono::system_clock::to_time_t(now);
|
||||
|
||||
mem_size = sizeof(SharedData) + sizeof(TriggerData);
|
||||
|
||||
Debug(1, "link.mem.size=%jd", static_cast<intmax_t>(mem_size));
|
||||
#if ZM_MEM_MAPPED
|
||||
map_fd = open(mem_file.c_str(), O_RDWR, (mode_t)0600);
|
||||
if (map_fd < 0) {
|
||||
Debug(3, "Can't open linked memory map file %s: %s", mem_file.c_str(), strerror(errno));
|
||||
disconnect();
|
||||
return false;
|
||||
}
|
||||
while (map_fd <= 2) {
|
||||
int new_map_fd = dup(map_fd);
|
||||
Warning("Got one of the stdio fds for our mmap handle. map_fd was %d, new one is %d", map_fd, new_map_fd);
|
||||
close(map_fd);
|
||||
map_fd = new_map_fd;
|
||||
}
|
||||
|
||||
struct stat map_stat;
|
||||
if (fstat(map_fd, &map_stat) < 0) {
|
||||
Error("Can't stat linked memory map file %s: %s", mem_file.c_str(), strerror(errno));
|
||||
disconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (map_stat.st_size == 0) {
|
||||
Error("Linked memory map file %s is empty: %s", mem_file.c_str(), strerror(errno));
|
||||
disconnect();
|
||||
return false;
|
||||
} else if (map_stat.st_size < mem_size) {
|
||||
Error("Got unexpected memory map file size %ld, expected %jd", map_stat.st_size, static_cast<intmax_t>(mem_size));
|
||||
disconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0);
|
||||
if (mem_ptr == MAP_FAILED) {
|
||||
Error("Can't map file %s (%jd bytes) to memory: %s", mem_file.c_str(), static_cast<intmax_t>(mem_size), strerror(errno));
|
||||
disconnect();
|
||||
return false;
|
||||
}
|
||||
#else // ZM_MEM_MAPPED
|
||||
shm_id = shmget((config.shm_key&0xffff0000)|id, mem_size, 0700);
|
||||
if (shm_id < 0) {
|
||||
Debug(3, "Can't shmget link memory: %s", strerror(errno));
|
||||
connected = false;
|
||||
return false;
|
||||
}
|
||||
mem_ptr = (unsigned char *)shmat(shm_id, 0, 0);
|
||||
if ((int)mem_ptr == -1) {
|
||||
Debug(3, "Can't shmat link memory: %s", strerror(errno));
|
||||
connected = false;
|
||||
return false;
|
||||
}
|
||||
#endif // ZM_MEM_MAPPED
|
||||
|
||||
shared_data = (SharedData *)mem_ptr;
|
||||
trigger_data = (TriggerData *)((char *)shared_data + sizeof(SharedData));
|
||||
|
||||
if (!shared_data->valid) {
|
||||
Debug(3, "Linked memory not initialised by capture daemon");
|
||||
disconnect();
|
||||
return false;
|
||||
}
|
||||
|
||||
last_state = shared_data->state;
|
||||
last_event_id = shared_data->last_event_id;
|
||||
connected = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} // end bool Monitor::MonitorLink::connect()
|
||||
|
||||
bool Monitor::MonitorLink::disconnect() {
|
||||
if (connected) {
|
||||
connected = false;
|
||||
|
||||
#if ZM_MEM_MAPPED
|
||||
if (mem_ptr > (void *)0) {
|
||||
msync(mem_ptr, mem_size, MS_ASYNC);
|
||||
munmap(mem_ptr, mem_size);
|
||||
}
|
||||
if (map_fd >= 0)
|
||||
close(map_fd);
|
||||
|
||||
map_fd = -1;
|
||||
#else // ZM_MEM_MAPPED
|
||||
struct shmid_ds shm_data;
|
||||
if (shmctl(shm_id, IPC_STAT, &shm_data) < 0) {
|
||||
Debug(3, "Can't shmctl: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
shm_id = 0;
|
||||
|
||||
if (shm_data.shm_nattch <= 1) {
|
||||
if (shmctl(shm_id, IPC_RMID, 0) < 0) {
|
||||
Debug(3, "Can't shmctl: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (shmdt(mem_ptr) < 0) {
|
||||
Debug(3, "Can't shmdt: %s", strerror(errno));
|
||||
return false;
|
||||
}
|
||||
#endif // ZM_MEM_MAPPED
|
||||
mem_size = 0;
|
||||
mem_ptr = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Monitor::MonitorLink::isAlarmed() {
|
||||
if (!connected) {
|
||||
return false;
|
||||
}
|
||||
return( shared_data->state == ALARM );
|
||||
}
|
||||
|
||||
bool Monitor::MonitorLink::inAlarm() {
|
||||
if (!connected) {
|
||||
return false;
|
||||
}
|
||||
return( shared_data->state == ALARM || shared_data->state == ALERT );
|
||||
}
|
||||
|
||||
bool Monitor::MonitorLink::hasAlarmed() {
|
||||
if (shared_data->state == ALARM) {
|
||||
return true;
|
||||
}
|
||||
last_event_id = shared_data->last_event_id;
|
||||
return false;
|
||||
}
|
|
@ -117,15 +117,15 @@ bool PacketQueue::queuePacket(std::shared_ptr<ZMPacket> add_packet) {
|
|||
|
||||
for (
|
||||
auto it = ++pktQueue.begin();
|
||||
it != pktQueue.end() and *it != add_packet;
|
||||
//it != pktQueue.end() and // can't git end because we added our packet
|
||||
*it != add_packet;
|
||||
// iterator is incremented by erase
|
||||
) {
|
||||
std::shared_ptr <ZMPacket>zm_packet = *it;
|
||||
|
||||
ZMLockedPacket *lp = new ZMLockedPacket(zm_packet);
|
||||
if (!lp->trylock()) {
|
||||
ZMLockedPacket lp(zm_packet);
|
||||
if (!lp.trylock()) {
|
||||
Warning("Found locked packet when trying to free up video packets. This basically means that decoding is not keeping up.");
|
||||
delete lp;
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
|
@ -137,7 +137,7 @@ bool PacketQueue::queuePacket(std::shared_ptr<ZMPacket> add_packet) {
|
|||
) {
|
||||
auto iterator_it = *iterators_it;
|
||||
// Have to check each iterator and make sure it doesn't point to the packet we are about to delete
|
||||
if ((*iterator_it!=pktQueue.end()) and (*(*iterator_it) == zm_packet)) {
|
||||
if (*(*iterator_it) == zm_packet) {
|
||||
Debug(1, "Bumping IT because it is at the front that we are deleting");
|
||||
++(*iterator_it);
|
||||
}
|
||||
|
@ -154,8 +154,6 @@ bool PacketQueue::queuePacket(std::shared_ptr<ZMPacket> add_packet) {
|
|||
max_video_packet_count,
|
||||
pktQueue.size());
|
||||
|
||||
delete lp;
|
||||
|
||||
if (zm_packet->packet.stream_index == video_stream_id)
|
||||
break;
|
||||
} // end while
|
||||
|
@ -163,7 +161,7 @@ bool PacketQueue::queuePacket(std::shared_ptr<ZMPacket> add_packet) {
|
|||
} // end lock scope
|
||||
// We signal on every packet because someday we may analyze sound
|
||||
Debug(4, "packetqueue queuepacket, unlocked signalling");
|
||||
condition.notify_all();
|
||||
condition.notify_one();
|
||||
|
||||
return true;
|
||||
} // end bool PacketQueue::queuePacket(ZMPacket* zm_packet)
|
||||
|
|
|
@ -214,22 +214,24 @@ class Event extends ZM_Object {
|
|||
}
|
||||
} # end Event->delete
|
||||
|
||||
public function getStreamSrc( $args=array(), $querySep='&' ) {
|
||||
|
||||
$streamSrc = '';
|
||||
$Server = null;
|
||||
public function Server() {
|
||||
if ( $this->Storage()->ServerId() ) {
|
||||
# The Event may have been moved to Storage on another server,
|
||||
# So prefer viewing the Event from the Server that is actually
|
||||
# storing the video
|
||||
$Server = $this->Storage()->Server();
|
||||
return $this->Storage()->Server();
|
||||
} else if ( $this->Monitor()->ServerId() ) {
|
||||
# Assume that the server that recorded it has it
|
||||
$Server = $this->Monitor()->Server();
|
||||
} else {
|
||||
# A default Server will result in the use of ZM_DIR_EVENTS
|
||||
$Server = new Server();
|
||||
return $this->Monitor()->Server();
|
||||
}
|
||||
# A default Server will result in the use of ZM_DIR_EVENTS
|
||||
return new Server();
|
||||
}
|
||||
|
||||
public function getStreamSrc( $args=array(), $querySep='&' ) {
|
||||
|
||||
$streamSrc = '';
|
||||
$Server = $this->Server();
|
||||
|
||||
# If we are in a multi-port setup, then use the multiport, else by
|
||||
# passing null Server->Url will use the Port set in the Server setting
|
||||
|
@ -354,15 +356,7 @@ class Event extends ZM_Object {
|
|||
# We always store at least 1 image when capturing
|
||||
|
||||
$streamSrc = '';
|
||||
$Server = null;
|
||||
if ( $this->Storage()->ServerId() ) {
|
||||
$Server = $this->Storage()->Server();
|
||||
} else if ( $this->Monitor()->ServerId() ) {
|
||||
# Assume that the server that recorded it has it
|
||||
$Server = $this->Monitor()->Server();
|
||||
} else {
|
||||
$Server = new Server();
|
||||
}
|
||||
$Server = $this->Server();
|
||||
$streamSrc .= $Server->UrlToIndex(
|
||||
ZM_MIN_STREAMING_PORT ?
|
||||
ZM_MIN_STREAMING_PORT+$this->{'MonitorId'} :
|
||||
|
@ -514,7 +508,7 @@ class Event extends ZM_Object {
|
|||
return false;
|
||||
}
|
||||
$Storage= $this->Storage();
|
||||
$Server = $Storage->ServerId() ? $Storage->Server() : $this->Monitor()->Server();
|
||||
$Server = $this->Server();
|
||||
if ( $Server->Id() != ZM_SERVER_ID ) {
|
||||
|
||||
$url = $Server->UrlToApi() . '/events/'.$this->{'Id'}.'.json';
|
||||
|
@ -562,7 +556,7 @@ class Event extends ZM_Object {
|
|||
return false;
|
||||
}
|
||||
$Storage= $this->Storage();
|
||||
$Server = $Storage->ServerId() ? $Storage->Server() : $this->Monitor()->Server();
|
||||
$Server = $this->Server();
|
||||
if ( $Server->Id() != ZM_SERVER_ID ) {
|
||||
|
||||
$url = $Server->UrlToApi().'/events/'.$this->{'Id'}.'.json';
|
||||
|
|
|
@ -759,7 +759,7 @@ class Monitor extends ZM_Object {
|
|||
}
|
||||
function Model() {
|
||||
if (!property_exists($this, 'Model')) {
|
||||
if ($this->{'ModelId'}) {
|
||||
if (property_exists($this, 'ModelId') and $this->{'ModelId'}) {
|
||||
$this->{'Model'} = Model::find_one(array('Id'=>$this->ModelId()));
|
||||
if (!$this->{'Model'})
|
||||
$this->{'Model'} = new Model();
|
||||
|
@ -771,7 +771,7 @@ class Monitor extends ZM_Object {
|
|||
}
|
||||
function Manufacturer() {
|
||||
if (!property_exists($this, 'Manufacturer')) {
|
||||
if ($this->{'ManufacturerId'}) {
|
||||
if (property_exists($this, 'ManufacturerId') and $this->{'ManufacturerId'}) {
|
||||
$this->{'Manufacturer'} = Manufacturer::find_one(array('Id'=>$this->ManufacturerId()));
|
||||
if (!$this->{'Manufacturer'})
|
||||
$this->{'Manufacturer'} = new Manufacturer();
|
||||
|
|
|
@ -237,10 +237,13 @@ class ZM_Object {
|
|||
$changes = array();
|
||||
|
||||
if ($defaults) {
|
||||
// FIXME: This code basically means that the new_values must be a full object, not a subset
|
||||
// Perhaps if it only concerned itself with the keys of new_values
|
||||
foreach ($defaults as $field => $type) {
|
||||
if (isset($new_values[$field])) continue;
|
||||
|
||||
if (isset($this->defaults[$field])) {
|
||||
//Debug("Setting default for $field");
|
||||
if (is_array($this->defaults[$field])) {
|
||||
$new_values[$field] = $this->defaults[$field]['default'];
|
||||
} else {
|
||||
|
@ -255,9 +258,11 @@ class ZM_Object {
|
|||
if (array_key_exists($field, $this->defaults) && is_array($this->defaults[$field]) && isset($this->defaults[$field]['filter_regexp'])) {
|
||||
if (is_array($this->defaults[$field]['filter_regexp'])) {
|
||||
foreach ($this->defaults[$field]['filter_regexp'] as $regexp) {
|
||||
//Debug("regexping array $field $value to " . preg_replace($regexp, '', trim($value)));
|
||||
$value = preg_replace($regexp, '', trim($value));
|
||||
}
|
||||
} else {
|
||||
//Debug("regexping $field $value to " . preg_replace($this->defaults[$field]['filter_regexp'], '', trim($value)));
|
||||
$value = preg_replace($this->defaults[$field]['filter_regexp'], '', trim($value));
|
||||
}
|
||||
}
|
||||
|
@ -265,10 +270,12 @@ class ZM_Object {
|
|||
$old_value = $this->$field();
|
||||
if (is_array($old_value)) {
|
||||
$diff = array_recursive_diff($old_value, $value);
|
||||
//Debug("$field array old: " .print_r($old_value, true) . " new: " . print_r($value, true). ' diff: '. print_r($diff, true));
|
||||
if ( count($diff) ) {
|
||||
$changes[$field] = $value;
|
||||
}
|
||||
} else if ( $this->$field() != $value ) {
|
||||
//Debug("$field != $value");
|
||||
$changes[$field] = $value;
|
||||
}
|
||||
} else if (property_exists($this, $field)) {
|
||||
|
|
|
@ -48,6 +48,7 @@ if (isset($_REQUEST['object']) and ($_REQUEST['object'] == 'filter')) {
|
|||
$_REQUEST['filter']['Query']['sort_field'] = validStr($_REQUEST['filter']['Query']['sort_field']);
|
||||
$_REQUEST['filter']['Query']['sort_asc'] = validStr($_REQUEST['filter']['Query']['sort_asc']);
|
||||
$_REQUEST['filter']['Query']['limit'] = validInt($_REQUEST['filter']['Query']['limit']);
|
||||
$_REQUEST['filter']['Query']['skip_locked'] = isset($_REQUEST['filter']['Query']['skip_locked']) ? validInt($_REQUEST['filter']['Query']['skip_locked']) : 0;
|
||||
|
||||
$_REQUEST['filter']['AutoCopy'] = empty($_REQUEST['filter']['AutoCopy']) ? 0 : 1;
|
||||
$_REQUEST['filter']['AutoCopyTo'] = empty($_REQUEST['filter']['AutoCopyTo']) ? 0 : $_REQUEST['filter']['AutoCopyTo'];
|
||||
|
@ -80,21 +81,23 @@ if (isset($_REQUEST['object']) and ($_REQUEST['object'] == 'filter')) {
|
|||
$error_message = $filter->get_last_error();
|
||||
return;
|
||||
}
|
||||
if ($action == 'Save' or $action == 'SaveAs' ) {
|
||||
// We update the request id so that the newly saved filter is auto-selected
|
||||
$_REQUEST['Id'] = $filter->Id();
|
||||
}
|
||||
} # end if changes
|
||||
|
||||
if ($action == 'execute') {
|
||||
$filter->execute();
|
||||
if (count($changes)) {
|
||||
$filter->delete();
|
||||
$filter->Id(null);
|
||||
$filter->Id($_REQUEST['Id']);
|
||||
}
|
||||
} else if ($filter->Background()) {
|
||||
$filter->control('start');
|
||||
}
|
||||
global $redirect;
|
||||
$redirect = '?view=filter'.$filter->querystring('filter', '&');
|
||||
$redirect = '?view=filter&Id='.$_REQUEST['Id'].$filter->querystring('filter', '&');
|
||||
|
||||
} else if ($action == 'control') {
|
||||
if ( $_REQUEST['command'] == 'start'
|
||||
|
|
|
@ -2414,4 +2414,70 @@ function i18n() {
|
|||
|
||||
return implode('-', $string);
|
||||
}
|
||||
|
||||
function get_networks() {
|
||||
$interfaces = array();
|
||||
|
||||
exec('ip link', $output, $status);
|
||||
if ( $status ) {
|
||||
$html_output = implode('<br/>', $output);
|
||||
ZM\Error("Unable to list network interfaces, status is '$status'. Output was:<br/><br/>$html_output");
|
||||
} else {
|
||||
foreach ( $output as $line ) {
|
||||
if ( preg_match('/^\d+: ([[:alnum:]]+):/', $line, $matches ) ) {
|
||||
if ( $matches[1] != 'lo' ) {
|
||||
$interfaces[$matches[1]] = $matches[1];
|
||||
} else {
|
||||
ZM\Debug("No match for $line");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$routes = array();
|
||||
exec('ip route', $output, $status);
|
||||
if ( $status ) {
|
||||
$html_output = implode('<br/>', $output);
|
||||
ZM\Error("Unable to list network interfaces, status is '$status'. Output was:<br/><br/>$html_output");
|
||||
} else {
|
||||
foreach ( $output as $line ) {
|
||||
if ( preg_match('/^default via [.[:digit:]]+ dev ([[:alnum:]]+)/', $line, $matches) ) {
|
||||
$interfaces['default'] = $matches[1];
|
||||
} else if ( preg_match('/^([.[:digit:]]+\/[[:digit:]]+) dev ([[:alnum:]]+)/', $line, $matches) ) {
|
||||
$interfaces[$matches[2]] .= ' ' . $matches[1];
|
||||
ZM\Debug("Matched $line: $matches[2] .= $matches[1]");
|
||||
} else {
|
||||
ZM\Debug("Didn't match $line");
|
||||
}
|
||||
} # end foreach line of output
|
||||
}
|
||||
return $interfaces;
|
||||
}
|
||||
|
||||
# Returns an array of subnets like 192.168.1.0/24 for a given interface.
|
||||
# Will ignore mdns networks.
|
||||
|
||||
function get_subnets($interface) {
|
||||
$subnets = array();
|
||||
exec('ip route', $output, $status);
|
||||
if ( $status ) {
|
||||
$html_output = implode('<br/>', $output);
|
||||
ZM\Error("Unable to list network interfaces, status is '$status'. Output was:<br/><br/>$html_output");
|
||||
} else {
|
||||
foreach ($output as $line) {
|
||||
if (preg_match('/^([.[:digit:]]+\/[[:digit:]]+) dev ([[:alnum:]]+)/', $line, $matches)) {
|
||||
if ($matches[1] == '169.254.0.0/16') {
|
||||
# Ignore mdns
|
||||
} else if ($matches[2] == $interface) {
|
||||
$subnets[] = $matches[1];
|
||||
} else {
|
||||
ZM\Debug("Wrong interface $matches[1] != $interface");
|
||||
}
|
||||
} else {
|
||||
ZM\Debug("Didn't match $line");
|
||||
}
|
||||
} # end foreach line of output
|
||||
}
|
||||
return $subnets;
|
||||
}
|
||||
|
||||
?>
|
||||
|
|
|
@ -472,6 +472,10 @@ function getRamHTML() {
|
|||
} else if ($mem_used_percent > 90) {
|
||||
$used_class = 'text-warning';
|
||||
}
|
||||
$result .= ' <li id="getRamHTML" class="nav-item dropdown mx-2">'.
|
||||
'<span class="'.$used_class.'" title="' .human_filesize($mem_used). ' of ' .human_filesize($meminfo['MemTotal']). '">'.translate('Memory').': '.$mem_used_percent.'%</span> ';
|
||||
|
||||
if ($meminfo['SwapTotal']) {
|
||||
$swap_used = $meminfo['SwapTotal'] - $meminfo['SwapFree'];
|
||||
$swap_used_percent = (int)(100*$swap_used/$meminfo['SwapTotal']);
|
||||
$swap_class = '';
|
||||
|
@ -480,11 +484,9 @@ function getRamHTML() {
|
|||
} else if ($swap_used_percent > 90) {
|
||||
$swap_class = 'text-warning';
|
||||
}
|
||||
|
||||
$result .= ' <li id="getRamHTML" class="nav-item dropdown mx-2">'.
|
||||
'<span class="'.$used_class.'" title="' .human_filesize($mem_used). ' of ' .human_filesize($meminfo['MemTotal']). '">'.translate('Memory').': '.$mem_used_percent.'%</span> '.
|
||||
'<span class="'.$swap_class.'" title="' .human_filesize($swap_used). ' of ' .human_filesize($meminfo['SwapTotal']). '">'.translate('Swap').': '.$swap_used_percent.'%</span> '.
|
||||
'</li>'.PHP_EOL;
|
||||
$result .= '<span class="'.$swap_class.'" title="' .human_filesize($swap_used). ' of ' .human_filesize($meminfo['SwapTotal']). '">'.translate('Swap').': '.$swap_used_percent.'%</span> ';
|
||||
} # end if SwapTotal
|
||||
$result .= '</li>'.PHP_EOL;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
|
|
@ -204,7 +204,10 @@ function changeScale() {
|
|||
streamScale(scale == '0' ? autoScale : scale);
|
||||
drawProgressBar();
|
||||
}
|
||||
alarmCue.html(renderAlarmCues(eventViewer));//just re-render alarmCues. skip ajax call
|
||||
if (cueFrames) {
|
||||
//just re-render alarmCues. skip ajax call
|
||||
alarmCue.html(renderAlarmCues(eventViewer));
|
||||
}
|
||||
setCookie('zmEventScale'+eventData.MonitorId, scale, 3600);
|
||||
|
||||
// After a resize, check if we still have room to display the event stats table
|
||||
|
|
|
@ -87,7 +87,7 @@ var eventDataStrings = {
|
|||
Emailed: '<?php echo translate('Emailed') ?>'
|
||||
};
|
||||
|
||||
var monitorUrl = '<?php echo $Event->Storage()->Server()->UrlToIndex(); ?>';
|
||||
var monitorUrl = '<?php echo $Event->Server()->UrlToIndex(); ?>';
|
||||
|
||||
var filterQuery = '<?php echo isset($filterQuery)?validJsStr(htmlspecialchars_decode($filterQuery)):'' ?>';
|
||||
var sortQuery = '<?php echo isset($sortQuery)?validJsStr(htmlspecialchars_decode($sortQuery)):'' ?>';
|
||||
|
|
|
@ -11,3 +11,7 @@ function configureButtons( element ) {
|
|||
var form = element.form;
|
||||
form.saveBtn.disabled = (form.probe.selectedIndex==0);
|
||||
}
|
||||
|
||||
function changeInterface(element) {
|
||||
element.form.submit();
|
||||
}
|
||||
|
|
|
@ -194,6 +194,21 @@ function probeActi($ip) {
|
|||
return $camera;
|
||||
}
|
||||
|
||||
function probeHikvision($ip) {
|
||||
$url = 'rtsp://admin:password@'.$ip.':554/Streaming/Channels/101?transportmode=unicast';
|
||||
$camera = array(
|
||||
'model' => 'Unknown Hikvision Camera',
|
||||
'monitor' => array(
|
||||
'Type' => 'FFmpeg',
|
||||
'Path' => $url,
|
||||
'Colours' => 4,
|
||||
'Width' => 1920,
|
||||
'Height' => 1080,
|
||||
),
|
||||
);
|
||||
return $camera;
|
||||
}
|
||||
|
||||
function probeVivotek($ip) {
|
||||
$url = 'http://'.$ip.'/cgi-bin/viewer/getparam.cgi';
|
||||
$camera = array(
|
||||
|
@ -244,20 +259,60 @@ function probeWansview($ip) {
|
|||
return $camera;
|
||||
}
|
||||
|
||||
function probeNetwork() {
|
||||
$cameras = array();
|
||||
function get_arp_results() {
|
||||
$results = array();
|
||||
$arp_command = ZM_PATH_ARP;
|
||||
$result = explode(' ', $arp_command);
|
||||
if ( !is_executable($result[0]) ) {
|
||||
ZM\Error('ARP compatible binary not found or not executable by the web user account. Verify ZM_PATH_ARP points to a valid arp tool.');
|
||||
return $cameras;
|
||||
return $results;
|
||||
}
|
||||
if (count($result)==1) {
|
||||
$arp_command .= ' -n';
|
||||
}
|
||||
|
||||
$result = exec(escapeshellcmd($arp_command), $output, $status);
|
||||
if ($status) {
|
||||
ZM\Error("Unable to probe network cameras, status is '$status'");
|
||||
return $cameras;
|
||||
return $results;
|
||||
}
|
||||
foreach ($output as $line) {
|
||||
if ( !preg_match('/(\d+\.\d+\.\d+\.\d+).*(([0-9a-f]{2}:){5})/', $line, $matches) ) {
|
||||
ZM\Debug("Didn't match preg $line");
|
||||
continue;
|
||||
}
|
||||
$results[$matches[2]] = $matches[1]; // results[mac] = ip
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
function get_arp_scan_results($network) {
|
||||
ZM\Debug("arp-scanning $network");
|
||||
$results = array();
|
||||
$arp_scan_command = ZM_PATH_ARP_SCAN;
|
||||
$result = explode(' ', $arp_scan_command);
|
||||
if (!is_executable($result[0])) {
|
||||
ZM\Error('arp-scan compatible binary not found or not executable by the web user account. Verify ZM_PATH_ARP_SCAN points to a valid arp-scan tool.');
|
||||
return $results;
|
||||
}
|
||||
$arp_scan_command = '/usr/bin/pkexec '.ZM_PATH_ARP_SCAN.' '.$network.' 2>&1';
|
||||
$result = exec(escapeshellcmd($arp_scan_command), $output, $status);
|
||||
if ($status) {
|
||||
ZM\Error("Unable to probe network cameras, command was $arp_scan_command, status is '$status' output: ".implode(PHP_EOL, $output));
|
||||
return $results;
|
||||
}
|
||||
foreach ($output as $line) {
|
||||
if (preg_match('/(\d+\.\d+\.\d+\.\d+)\s+(([0-9a-f]{2}:){5})/', $line, $matches)) {
|
||||
$results[$matches[2]] = $matches[1];
|
||||
} else {
|
||||
ZM\Debug("Didn't match preg $line");
|
||||
}
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
function probeNetwork() {
|
||||
$cameras = array();
|
||||
|
||||
$monitors = array();
|
||||
foreach ( dbFetchAll("SELECT `Id`, `Name`, `Host` FROM `Monitors` WHERE `Type` = 'Remote' ORDER BY `Host`") as $monitor ) {
|
||||
|
@ -269,25 +324,26 @@ function probeNetwork() {
|
|||
$monitors[gethostbyname($monitor['Host'])] = $monitor;
|
||||
}
|
||||
}
|
||||
foreach ( dbFetchAll("SELECT `Id`, `Name`, `Path` FROM `Monitors` WHERE `Type` = 'Ffmpeg' ORDER BY `Path`") as $monitor ) {
|
||||
$url_parts = parse_url($monitor['Path']);
|
||||
ZM\Debug("Ffmpeg monitor ${url_parts['host']} = ${monitor['Id']} ${monitor['Name']}");
|
||||
$monitors[gethostbyname($url_parts['host'])] = $monitor;
|
||||
}
|
||||
|
||||
$macBases = array(
|
||||
'00:40:8c' => array('type'=>'Axis', 'probeFunc'=>'probeAxis'),
|
||||
'00:80:f0' => array('type'=>'Panasonic','probeFunc'=>'probePana'),
|
||||
'00:0f:7c' => array('type'=>'ACTi','probeFunc'=>'probeACTi'),
|
||||
'00:40:8c' => array('type'=>'Axis', 'probeFunc'=>'probeAxis'),
|
||||
'2c:a5:9c' => array('type'=>'Hikvision', 'probeFunc'=>'probeHikvision'),
|
||||
'00:80:f0' => array('type'=>'Panasonic','probeFunc'=>'probePana'),
|
||||
'00:02:d1' => array('type'=>'Vivotek','probeFunc'=>'probeVivotek'),
|
||||
'7c:dd:90' => array('type'=>'Wansview','probeFunc'=>'probeWansview'),
|
||||
'78:a5:dd' => array('type'=>'Wansview','probeFunc'=>'probeWansview')
|
||||
);
|
||||
|
||||
foreach ( $output as $line ) {
|
||||
if ( !preg_match('/(\d+\.\d+\.\d+\.\d+).*(([0-9a-f]{2}:){5})/', $line, $matches) )
|
||||
continue;
|
||||
$ip = $matches[1];
|
||||
$host = $ip;
|
||||
$mac = $matches[2];
|
||||
//echo "I:$ip, H:$host, M:$mac<br/>";
|
||||
foreach ( get_arp_results() as $mac=>$ip ) {
|
||||
$macRoot = substr($mac,0,8);
|
||||
if ( isset($macBases[$macRoot]) ) {
|
||||
ZM\Debug("Have match for $macRoot ".$macBases[$macRoot]['type']);
|
||||
$macBase = $macBases[$macRoot];
|
||||
$camera = call_user_func($macBase['probeFunc'], $ip);
|
||||
$sourceDesc = base64_encode(json_encode($camera['monitor']));
|
||||
|
@ -299,8 +355,36 @@ function probeNetwork() {
|
|||
$sourceString .= ' - '.translate('Available');
|
||||
}
|
||||
$cameras[$sourceDesc] = $sourceString;
|
||||
} else {
|
||||
ZM\Debug("No match for $macRoot");
|
||||
}
|
||||
} # end foreach output line
|
||||
|
||||
if (isset($_REQUEST['interface']) and $_REQUEST['interface']) {
|
||||
foreach (get_subnets($_REQUEST['interface']) as $network) {
|
||||
foreach ( get_arp_scan_results($network) as $mac=>$ip ) {
|
||||
$macRoot = substr($mac,0,8);
|
||||
ZM\Debug("Got $macRoot from $mac");
|
||||
if (isset($macBases[$macRoot])) {
|
||||
ZM\Debug("Have match for $macRoot $ip ".$macBases[$macRoot]['type']);
|
||||
$macBase = $macBases[$macRoot];
|
||||
$camera = call_user_func($macBase['probeFunc'], $ip);
|
||||
$sourceDesc = base64_encode(json_encode($camera['monitor']));
|
||||
$sourceString = $camera['model'].' @ '.$host;
|
||||
if (isset($monitors[$ip])) {
|
||||
$monitor = $monitors[$ip];
|
||||
$sourceString .= ' ('.$monitor['Name'].')';
|
||||
} else {
|
||||
$sourceString .= ' - '.translate('Available');
|
||||
}
|
||||
$cameras[$sourceDesc] = $sourceString;
|
||||
} else {
|
||||
ZM\Debug("No match for $macRoot");
|
||||
}
|
||||
} # end foreach output line
|
||||
} # end foreach network
|
||||
} # end if we have a network specified
|
||||
|
||||
return $cameras;
|
||||
} # end function probeNetwork()
|
||||
|
||||
|
@ -324,11 +408,25 @@ xhtmlHeaders(__FILE__, translate('MonitorProbe') );
|
|||
<h2><?php echo translate('MonitorProbe') ?></h2>
|
||||
<div id="content">
|
||||
<form name="contentForm" id="contentForm" method="get" action="?">
|
||||
<input type="hidden" name="view" value="none"/>
|
||||
<input type="hidden" name="mid" value="<?php echo isset($_REQUEST['mid'])?validNum($_REQUEST['mid']):'' ?>"/>
|
||||
<input type="hidden" name="view" value="monitorprobe"/>
|
||||
|
||||
<p>
|
||||
<?php echo translate('MonitorProbeIntro') ?>
|
||||
</p>
|
||||
<p><label for="interface"><?php echo translate('Interface') ?></label>
|
||||
<?php
|
||||
$interfaces = array('', 'select');
|
||||
$interfaces += get_networks();
|
||||
$default_interface = $interfaces['default'];
|
||||
unset($interfaces['default']);
|
||||
|
||||
echo htmlSelect('interface', $interfaces,
|
||||
(isset($_REQUEST['interface']) ? $_REQUEST['interface'] : $default_interface),
|
||||
array('data-on-change-this'=>'changeInterface') );
|
||||
|
||||
?>
|
||||
</p>
|
||||
<p>
|
||||
<label for="probe"><?php echo translate('DetectedCameras') ?></label>
|
||||
<?php echo htmlSelect('probe', $cameras, null, array('data-on-change-this'=>'configureButtons')); ?>
|
||||
|
|
|
@ -174,38 +174,9 @@ if ( !isset($_REQUEST['step']) || ($_REQUEST['step'] == '1') ) {
|
|||
</p>
|
||||
<p><label for="interface"><?php echo translate('Interface') ?></label>
|
||||
<?php
|
||||
$interfaces = array();
|
||||
$default_interface = null;
|
||||
|
||||
exec('ip link', $output, $status);
|
||||
if ( $status ) {
|
||||
$html_output = implode('<br/>', $output);
|
||||
ZM\Error("Unable to list network interfaces, status is '$status'. Output was:<br/><br/>$html_output");
|
||||
} else {
|
||||
foreach ( $output as $line ) {
|
||||
if ( preg_match('/^\d+: ([[:alnum:]]+):/', $line, $matches ) ) {
|
||||
if ( $matches[1] != 'lo' ) {
|
||||
$interfaces[$matches[1]] = $matches[1];
|
||||
} else {
|
||||
ZM\Debug("No match for $line");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$routes = array();
|
||||
exec('ip route', $output, $status);
|
||||
if ( $status ) {
|
||||
$html_output = implode('<br/>', $output);
|
||||
ZM\Error("Unable to list network interfaces, status is '$status'. Output was:<br/><br/>$html_output");
|
||||
} else {
|
||||
foreach ( $output as $line ) {
|
||||
if ( preg_match('/^default via [.[:digit:]]+ dev ([[:alnum:]]+)/', $line, $matches) ) {
|
||||
$default_interface = $matches[1];
|
||||
} else if ( preg_match('/^([.\/[:digit:]]+) dev ([[:alnum:]]+)/', $line, $matches) ) {
|
||||
$interfaces[$matches[2]] .= ' ' . $matches[1];
|
||||
}
|
||||
} # end foreach line of output
|
||||
}
|
||||
$interfaces = get_networks();
|
||||
$default_interface = $interfaces['default'];
|
||||
unset($interfaces['default']);
|
||||
|
||||
echo htmlSelect('interface', $interfaces,
|
||||
(isset($_REQUEST['interface']) ? $_REQUEST['interface'] : $default_interface),
|
||||
|
|
Loading…
Reference in New Issue