From 1333d8c7513be6771777ee49403d7afe7ada7ef8 Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 4 Jan 2022 11:58:19 -0600 Subject: [PATCH] Adds ONVIF Motion Detection Support --- CMakeLists.txt | 9 + cmake/Modules/FindGSOAP.cmake | 113 +++++++++++++ db/zm_create.sql.in | 1 + db/zm_update-1.37.7.sql | 19 +++ scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm | 7 + src/CMakeLists.txt | 75 +++++++++ src/zm_monitor.cpp | 167 ++++++++++++++++++- src/zm_monitor.h | 24 +++ src/zm_poll_thread.cpp | 32 ++++ src/zm_poll_thread.h | 29 ++++ web/includes/Monitor.php | 1 + web/skins/classic/views/monitor.php | 4 + 12 files changed, 479 insertions(+), 2 deletions(-) create mode 100644 cmake/Modules/FindGSOAP.cmake create mode 100644 src/zm_poll_thread.cpp create mode 100644 src/zm_poll_thread.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b789e4c8..0479ce1fd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -518,6 +518,15 @@ endif() #list(APPEND ZM_BIN_LIBS "${Boost_LIBRARIES}") #endif() + +find_package(GSOAP 2.0.0) +if (GSOAP_FOUND) + set(optlibsfound "${optlibsfound} gsoap") + add_compile_definitions(WITH_GSOAP) +else() + set(optlibsnotfound "${optlibsnotfound} gsoap") +endif() + if(NOT ZM_NO_RTSPSERVER) set(HAVE_RTSP_SERVER 1) else() diff --git a/cmake/Modules/FindGSOAP.cmake b/cmake/Modules/FindGSOAP.cmake new file mode 100644 index 000000000..c7f181bec --- /dev/null +++ b/cmake/Modules/FindGSOAP.cmake @@ -0,0 +1,113 @@ +# +# This module detects if gsoap is installed and determines where the +# include files and libraries are. +# +# This code sets the following variables: +# +# GSOAP_IMPORT_DIR = full path to the gsoap import directory +# GSOAP_LIBRARIES = full path to the gsoap libraries +# GSOAP_SSL_LIBRARIES = full path to the gsoap ssl libraries +# GSOAP_INCLUDE_DIR = include dir to be used when using the gsoap library +# GSOAP_PLUGIN_DIR = gsoap plugins directory +# GSOAP_WSDL2H = wsdl2h binary +# GSOAP_SOAPCPP2 = soapcpp2 binary +# GSOAP_FOUND = set to true if gsoap was found successfully +# +# GSOAP_ROOT +# setting this enables search for gsoap libraries / headers in this location + +# ----------------------------------------------------- +# GSOAP Import Directories +# ----------------------------------------------------- +find_path(GSOAP_IMPORT_DIR + NAMES wsa.h + PATHS ${GSOAP_ROOT}/import ${GSOAP_ROOT}/share/gsoap/import +) + +# ----------------------------------------------------- +# GSOAP Libraries +# ----------------------------------------------------- +find_library(GSOAP_CXX_LIBRARIES + NAMES gsoap++ + HINTS ${GSOAP_ROOT}/lib ${GSOAP_ROOT}/lib64 + ${GSOAP_ROOT}/lib32 + DOC "The main gsoap library" +) +find_library(GSOAP_SSL_CXX_LIBRARIES + NAMES gsoapssl++ + HINTS ${GSOAP_ROOT}/lib ${GSOAP_ROOT}/lib64 + ${GSOAP_ROOT}/lib32 + DOC "The ssl gsoap library" +) + + +# ----------------------------------------------------- +# GSOAP Include Directories +# ----------------------------------------------------- +find_path(GSOAP_INCLUDE_DIR + NAMES stdsoap2.h + HINTS ${GSOAP_ROOT} ${GSOAP_ROOT}/include ${GSOAP_ROOT}/include/* + DOC "The gsoap include directory" +) + +# ----------------------------------------------------- +# GSOAP plugin Directories +# ----------------------------------------------------- +find_path(GSOAP_PLUGIN_DIR + NAMES wsseapi.c + HINTS ${GSOAP_ROOT} /usr/share/gsoap/plugin + DOC "The gsoap plugin directory" +) + +# ----------------------------------------------------- +# GSOAP Binaries +# ---------------------------------------------------- +if(NOT GSOAP_TOOL_DIR) + set(GSOAP_TOOL_DIR GSOAP_ROOT) +endif() + +find_program(GSOAP_WSDL2H + NAMES wsdl2h + HINTS ${GSOAP_TOOL_DIR}/bin + DOC "The gsoap bin directory" +) +find_program(GSOAP_SOAPCPP2 + NAMES soapcpp2 + HINTS ${GSOAP_TOOL_DIR}/bin + DOC "The gsoap bin directory" +) +# ----------------------------------------------------- +# GSOAP version +# try to determine the flagfor the 2.7.6 compatiblity, break with 2.7.13 and re-break with 2.7.16 +# ---------------------------------------------------- +if(GSOAP_SOAPCPP2) + execute_process(COMMAND ${GSOAP_SOAPCPP2} "-V" OUTPUT_VARIABLE GSOAP_STRING_VERSION ERROR_VARIABLE GSOAP_STRING_VERSION ) + string(REGEX MATCH "[0-9]*\\.[0-9]*\\.[0-9]*" GSOAP_VERSION ${GSOAP_STRING_VERSION}) +endif() +# ----------------------------------------------------- +# GSOAP_276_COMPAT_FLAGS and GSOAPVERSION +# try to determine the flagfor the 2.7.6 compatiblity, break with 2.7.13 and re-break with 2.7.16 +# ---------------------------------------------------- +if( "${GSOAP_VERSION}" VERSION_LESS "2.7.6") + set(GSOAP_276_COMPAT_FLAGS "") +elseif ( "${GSOAP_VERSION}" VERSION_LESS "2.7.14") + set(GSOAP_276_COMPAT_FLAGS "-z") +else ( "${GSOAP_VERSION}" VERSION_LESS "2.7.14") + set(GSOAP_276_COMPAT_FLAGS "-z1 -z2") +endif ( "${GSOAP_VERSION}" VERSION_LESS "2.7.6") + +# ----------------------------------------------------- +# handle the QUIETLY and REQUIRED arguments and set GSOAP_FOUND to TRUE if +# all listed variables are TRUE +# ----------------------------------------------------- +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(GSOAP DEFAULT_MSG GSOAP_CXX_LIBRARIES + GSOAP_INCLUDE_DIR GSOAP_WSDL2H GSOAP_SOAPCPP2) +mark_as_advanced(GSOAP_INCLUDE_DIR GSOAP_LIBRARIES GSOAP_WSDL2H GSOAP_SOAPCPP2) + +if(GSOAP_FOUND) + if(GSOAP_FIND_REQUIRED AND GSOAP_FIND_VERSION AND ${GSOAP_VERSION} VERSION_LESS ${GSOAP_FIND_VERSION}) + message(SEND_ERROR "Found GSOAP version ${GSOAP_VERSION} less then required ${GSOAP_FIND_VERSION}.") + endif() +endif() + diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index 72b0be56f..110365125 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -463,6 +463,7 @@ CREATE TABLE `Monitors` ( `ONVIF_Username` VARCHAR(64) NOT NULL DEFAULT '', `ONVIF_Password` VARCHAR(64) NOT NULL DEFAULT '', `ONVIF_Options` VARCHAR(64) NOT NULL DEFAULT '', + `ONVIF_Event_Listener` BOOLEAN NOT NULL DEFAULT FALSE, `Device` tinytext NOT NULL default '', `Channel` tinyint(3) unsigned NOT NULL default '0', `Format` int(10) unsigned NOT NULL default '0', diff --git a/db/zm_update-1.37.7.sql b/db/zm_update-1.37.7.sql index 7d45f67c6..39000e912 100644 --- a/db/zm_update-1.37.7.sql +++ b/db/zm_update-1.37.7.sql @@ -1,2 +1,21 @@ /* Change Cause from varchar(32) to TEXT. We now include alarmed zone name */ ALTER TABLE `Events` MODIFY `Cause` TEXT; + +-- +-- Update Monitors table to have a ONVIF_Event_Listener Column +-- + +SELECT 'Checking for ONVIF_Event_Listener in Monitors'; +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'ONVIF_Event_Listener' + ) > 0, +"SELECT 'Column ONVIF_Event_Listener already exists in Monitorss'", +"ALTER TABLE `Monitors` ADD COLUMN `ONVIF_Event_Listener` BOOLEAN NOT NULL default false AFTER `ONVIF_Options`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm index 25db048fc..54ce25c87 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm @@ -56,6 +56,13 @@ $serial = $primary_key = 'Id'; Enabled LinkedMonitors Triggers + EventStartCommand + EventEndCommand + ONVIF_URL + ONVIF_Username + ONVIF_Password + ONVIF_Options + ONVIF_Event_Listener Device Channel Format diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8852391a4..6878a6589 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,6 +6,7 @@ configure_file(zm_config_data.h.in "${CMAKE_BINARY_DIR}/zm_config_data.h" @ONLY) # Group together all the source files that are used by all the binaries (zmc, zmu, zms etc) set(ZM_BIN_SRC_FILES zm_analysis_thread.cpp + zm_poll_thread.cpp zm_buffer.cpp zm_camera.cpp zm_comms.cpp @@ -67,6 +68,57 @@ set(ZM_BIN_SRC_FILES zm_zone.cpp zm_storage.cpp) +if(GSOAP_FOUND) + set(ZM_BIN_SRC_FILES + ${ZM_BIN_SRC_FILES} + ${CMAKE_BINARY_DIR}/generated/soapPullPointSubscriptionBindingProxy.cpp + ${CMAKE_BINARY_DIR}/generated/soapC.cpp + ${GSOAP_PLUGIN_DIR}/smdevp.c + ${GSOAP_PLUGIN_DIR}/mecevp.c + ${GSOAP_PLUGIN_DIR}/wsaapi.c + ${GSOAP_PLUGIN_DIR}/wsseapi.c + ${GSOAP_PLUGIN_DIR}/../custom/struct_timeval.c + ) + + SET(GCC_COMPILE_FLAGS "-DWITH_OPENSSL -DWITH_DOM") + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GCC_COMPILE_FLAGS}") + + #Create the directory that will host files generated by GSOAP + file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/generated) + + #some files are generated by gsoap + set_source_files_properties( ${CMAKE_BINARY_DIR}/generated/soapClientLib.c PROPERTIES GENERATED TRUE ) + set_source_files_properties( ${CMAKE_BINARY_DIR}/generated/soapC.c PROPERTIES GENERATED TRUE ) + set_source_files_properties( ${CMAKE_BINARY_DIR}/generated/soapPullPointSubscriptionBindingProxy.cpp PROPERTIES GENERATED TRUE ) + set_source_files_properties( ${GSOAP_PLUGIN_DIR}/smdevp.c PROPERTIES LANGUAGE CXX) + set_source_files_properties( ${GSOAP_PLUGIN_DIR}/mecevp.c PROPERTIES LANGUAGE CXX) + set_source_files_properties( ${GSOAP_PLUGIN_DIR}/wsaapi.c PROPERTIES LANGUAGE CXX) + set_source_files_properties( ${GSOAP_PLUGIN_DIR}/wsseapi.c PROPERTIES LANGUAGE CXX) + set_source_files_properties( ${GSOAP_PLUGIN_DIR}/../custom/struct_timeval.c PROPERTIES LANGUAGE CXX) + + #Create a cmake target that generate gsoap files + add_custom_command( + OUTPUT ${CMAKE_BINARY_DIR}/generated/soapC.cpp + OUTPUT ${CMAKE_BINARY_DIR}/generated/soapPullPointSubscriptionBindingProxy.cpp + COMMAND ${GSOAP_WSDL2H} -d -P -O2 -o ${CMAKE_BINARY_DIR}/generated/bindings.h http://www.onvif.org/onvif/ver10/events/wsdl/event.wsdl + COMMAND echo '\#import \"wsse.h\"' >> ${CMAKE_BINARY_DIR}/generated/bindings.h + COMMAND echo '\#import \"struct_timeval.h\"' >> ${CMAKE_BINARY_DIR}/generated/bindings.h + COMMAND ${GSOAP_SOAPCPP2} -n -2 -C -I ${GSOAP_PLUGIN_DIR}/.. -I ${GSOAP_PLUGIN_DIR}/../import/ -I ${GSOAP_PLUGIN_DIR}/../custom/ -d ${CMAKE_BINARY_DIR}/generated -j -x ${CMAKE_BINARY_DIR}/generated/bindings.h + COMMENT "CREATING STUBS AND GLUE CODE" + ) + + add_custom_target(GSOAP_GENERATION_TARGET + DEPENDS ${CMAKE_BINARY_DIR}/generated/soapC.cpp + DEPENDS ${CMAKE_BINARY_DIR}/generated/soapPullPointSubscriptionBindingProxy.cpp + DEPENDS ${GSOAP_PLUGIN_DIR}/smdevp.c + DEPENDS ${GSOAP_PLUGIN_DIR}/mecevp.c + DEPENDS ${GSOAP_PLUGIN_DIR}/wsaapi.c + DEPENDS ${GSOAP_PLUGIN_DIR}/wsseapi.c + DEPENDS ${GSOAP_PLUGIN_DIR}/../custom/struct_timeval.c + ) + +endif() + # A fix for cmake recompiling the source files for every target. add_library(zm STATIC ${ZM_BIN_SRC_FILES}) @@ -75,6 +127,15 @@ target_include_directories(zm ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) +if(GSOAP_FOUND) +target_include_directories(zm + PUBLIC + ${CMAKE_BINARY_DIR}/generated + ${GSOAP_PLUGIN_DIR}/.. + ${GSOAP_INCLUDE_DIR}) + +endif() + target_link_libraries(zm PUBLIC FFMPEG::avcodec @@ -89,6 +150,15 @@ target_link_libraries(zm PRIVATE zm-core-interface) +if(GSOAP_FOUND) + target_link_libraries(zm + PUBLIC + ${GSOAP_CXX_LIBRARIES} + ${GSOAP_SSL_CXX_LIBRARIES} + ${OPENSSL_SSL_LIBRARY} + ${OPENSSL_CRYPTO_LIBRARY}) +endif() + if(${ZM_JWT_BACKEND} STREQUAL "jwt_cpp") target_link_libraries(zm PUBLIC @@ -110,6 +180,11 @@ add_executable(zms zms.cpp) add_executable(zmu zmu.cpp) add_executable(zmbenchmark zmbenchmark.cpp) +if(GSOAP_FOUND) + #Make sure that the client is compiled only after gsoap has been processed + add_dependencies(zmc GSOAP_GENERATION_TARGET) +endif() + target_link_libraries(zmc PRIVATE zm-core-interface diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 52202e947..cfc88891f 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -66,6 +66,14 @@ #define MAP_LOCKED 0 #endif +#ifdef WITH_GSOAP +//Workaround for the gsoap library on RHEL +struct Namespace namespaces[] = +{ + {NULL, NULL} // end of table +}; +#endif + // This is the official SQL (and ordering of the fields) to load a Monitor. // It will be used whereever a Monitor dbrow is needed. WHERE conditions can be appended std::string load_monitor_sql = @@ -83,7 +91,7 @@ std::string load_monitor_sql = "`SectionLength`, `MinSectionLength`, `FrameSkip`, `MotionFrameSkip`, " "`FPSReportInterval`, `RefBlendPerc`, `AlarmRefBlendPerc`, `TrackMotion`, `Exif`," "`RTSPServer`, `RTSPStreamName`," -"`ONVIF_URL`, `ONVIF_Username`, `ONVIF_Password`, `ONVIF_Options`," +"`ONVIF_URL`, `ONVIF_Username`, `ONVIF_Password`, `ONVIF_Options`, `ONVIF_Event_Listener`, " "`SignalCheckPoints`, `SignalCheckColour`, `Importance`-1 FROM `Monitors`"; std::string CameraType_Strings[] = { @@ -446,7 +454,7 @@ Monitor::Monitor() "SectionLength, MinSectionLength, FrameSkip, MotionFrameSkip, " "FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif," "`RTSPServer`,`RTSPStreamName`, - "`ONVIF_URL`, `ONVIF_Username`, `ONVIF_Password`, `ONVIF_Options`," + "`ONVIF_URL`, `ONVIF_Username`, `ONVIF_Password`, `ONVIF_Options`, `ONVIF_Event_Listener`, " "SignalCheckPoints, SignalCheckColour, Importance-1 FROM Monitors"; */ @@ -621,6 +629,7 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { onvif_username = std::string(dbrow[col] ? dbrow[col] : ""); col++; onvif_password = std::string(dbrow[col] ? dbrow[col] : ""); col++; onvif_options = std::string(dbrow[col] ? dbrow[col] : ""); col++; + onvif_event_listener = (*dbrow[col] != '0'); col++; importance = dbrow[col] ? atoi(dbrow[col]) : 0;// col++; @@ -1059,6 +1068,42 @@ bool Monitor::connect() { return false; } + //ONVIF Setup +#ifdef WITH_GSOAP + ONVIF_Trigger_State = FALSE; + if (onvif_event_listener) { //Temporarily using this option to enable the feature + Debug(1, "Starting ONVIF"); + ONVIF_Healthy = FALSE; + tev__PullMessages.Timeout = "PT600S"; + tev__PullMessages.MessageLimit = 100; + soap = soap_new(); + soap->connect_timeout = 5; + soap->recv_timeout = 5; + soap->send_timeout = 5; + soap_register_plugin(soap, soap_wsse); + proxyEvent = PullPointSubscriptionBindingProxy(soap); + std::string full_url = onvif_url + "/Events"; + proxyEvent.soap_endpoint = full_url.c_str(); + set_credentials(soap); + Debug(1, "ONVIF Endpoint: %s", proxyEvent.soap_endpoint); + if (proxyEvent.CreatePullPointSubscription(&request, response) != SOAP_OK) { + Warning("Couldn't create subscription!"); + } else { + //Empty the stored messages + set_credentials(soap); + if (proxyEvent.PullMessages(response.SubscriptionReference.Address, NULL, &tev__PullMessages, tev__PullMessagesResponse) != SOAP_OK) { + Warning("Couldn't do initial event pull! %s", response.SubscriptionReference.Address); + } else { + Debug(1, "Good Initial ONVIF Pull"); + ONVIF_Healthy = TRUE; + } + } + } else { + Debug(1, "Not Starting ONVIF"); + } + //End ONVIF Setup +#endif + // We set these here because otherwise the first fps calc is meaningless last_fps_time = std::chrono::system_clock::now(); last_analysis_fps_time = std::chrono::system_clock::now(); @@ -1713,6 +1758,50 @@ void Monitor::UpdateFPS() { } // end if report fps } // 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() { + +#ifdef WITH_GSOAP + if (ONVIF_Healthy) { + set_credentials(soap); + int result = proxyEvent.PullMessages(response.SubscriptionReference.Address, NULL, &tev__PullMessages, tev__PullMessagesResponse); + if (result != SOAP_OK) { + if (result != -1) //Ignore the timeout error + Warning("Failed to get ONVIF messages! %i", result); + } else { + Debug(1, "Got Good Response! %i", result); + for (auto msg : tev__PullMessagesResponse.wsnt__NotificationMessage) { + if (msg->Topic->__any.text != NULL && + std::strstr(msg->Topic->__any.text, "MotionAlarm") && + msg->Message.__any.elts != NULL && + msg->Message.__any.elts->next != NULL && + msg->Message.__any.elts->next->elts != NULL && + msg->Message.__any.elts->next->elts->atts != NULL && + msg->Message.__any.elts->next->elts->atts->next != NULL && + msg->Message.__any.elts->next->elts->atts->next->text != NULL) { + Debug(1,"Got Motion Alarm!"); + if (strcmp(msg->Message.__any.elts->next->elts->atts->next->text, "true") == 0) { + //Event Start + Debug(1,"Triggered on ONVIF"); + if (!ONVIF_Trigger_State) { + Debug(1,"Triggered Event"); + ONVIF_Trigger_State = TRUE; + } + } else { + Debug(1, "Triggered off ONVIF"); + ONVIF_Trigger_State = FALSE; + } + } + } + } + } +#endif + return TRUE; +} //end Poll + + + // Would be nice if this JUST did analysis // This idea is that we should be analysing as close to the capture frame as possible. // This function should process as much as possible before returning @@ -1760,6 +1849,19 @@ bool Monitor::Analyse() { std::string cause; Event::StringSetMap noteSetMap; +#ifdef WITH_GSOAP + if (ONVIF_Trigger_State) { + score += 9; + Debug(1, "Triggered on ONVIF"); + if (!event) { + cause += "ONVIF"; + } + Event::StringSet noteSet; + noteSet.insert("ONVIF2"); + noteSetMap[MOTION_CAUSE] = noteSet; + } // end ONVIF_Trigger +#endif + // Specifically told to be on. Setting the score here will trigger the alarm. if (trigger_data->trigger_state == TriggerState::TRIGGER_ON) { score += trigger_data->trigger_score; @@ -2999,6 +3101,16 @@ int Monitor::PrimeCapture() { } } // end if rtsp_server +#ifdef WITH_GSOAP //For now, just don't run the thread if no ONVIF support. This may change if we add other long polling options. + //ONVIF Thread + if (onvif_event_listener) { + if (!Poller) { + Poller = zm::make_unique(this); + } else { + Poller->Start(); + } + } +#endif if (decoding_enabled) { if (!decoder_it) decoder_it = packetqueue.get_video_it(false); if (!decoder) { @@ -3033,6 +3145,24 @@ int Monitor::Close() { if (decoder) { decoder->Stop(); } + +#ifdef WITH_GSOAP + //ONVIF Teardown + if (Poller) { + Poller->Stop(); + } + if (onvif_event_listener && soap != nullptr) { + Debug(1, "Tearing Down Onvif"); + _wsnt__Unsubscribe wsnt__Unsubscribe; + _wsnt__UnsubscribeResponse wsnt__UnsubscribeResponse; + proxyEvent.Unsubscribe(response.SubscriptionReference.Address, NULL, &wsnt__Unsubscribe, wsnt__UnsubscribeResponse); + soap_destroy(soap); + soap_end(soap); + soap_free(soap); + soap = nullptr; + }//End ONVIF +#endif + if (analysis_thread) { analysis_thread->Stop(); } @@ -3138,3 +3268,36 @@ StringVector Monitor::GroupNames() { } return groupnames; } // end Monitor::GroupNames() + +#ifdef WITH_GSOAP +//ONVIF Set Credentials +void Monitor::set_credentials(struct soap *soap) +{ + soap_wsse_delete_Security(soap); + soap_wsse_add_Timestamp(soap, NULL, 10); + soap_wsse_add_UsernameTokenDigest(soap, "Auth", onvif_username.c_str(), onvif_password.c_str()); +} + +//GSOAP boilerplate +int SOAP_ENV__Fault(struct soap *soap, char *faultcode, char *faultstring, char *faultactor, struct SOAP_ENV__Detail *detail, struct SOAP_ENV__Code *SOAP_ENV__Code, struct SOAP_ENV__Reason *SOAP_ENV__Reason, char *SOAP_ENV__Node, char *SOAP_ENV__Role, struct SOAP_ENV__Detail *SOAP_ENV__Detail) +{ + // populate the fault struct from the operation arguments to print it + soap_fault(soap); + // SOAP 1.1 + soap->fault->faultcode = faultcode; + soap->fault->faultstring = faultstring; + soap->fault->faultactor = faultactor; + soap->fault->detail = detail; + // SOAP 1.2 + soap->fault->SOAP_ENV__Code = SOAP_ENV__Code; + soap->fault->SOAP_ENV__Reason = SOAP_ENV__Reason; + soap->fault->SOAP_ENV__Node = SOAP_ENV__Node; + soap->fault->SOAP_ENV__Role = SOAP_ENV__Role; + soap->fault->SOAP_ENV__Detail = SOAP_ENV__Detail; + // set error + soap->error = SOAP_FAULT; + // handle or display the fault here with soap_stream_fault(soap, std::cerr); + // return HTTP 202 Accepted + return soap_send_empty_response(soap, SOAP_OK); +} +#endif diff --git a/src/zm_monitor.h b/src/zm_monitor.h index 804750585..b1df66b90 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -23,6 +23,7 @@ #include "zm_define.h" #include "zm_camera.h" #include "zm_analysis_thread.h" +#include "zm_poll_thread.h" #include "zm_decoder_thread.h" #include "zm_event.h" #include "zm_fifo.h" @@ -34,6 +35,12 @@ #include #include +#ifdef WITH_GSOAP +#include "soapPullPointSubscriptionBindingProxy.h" +#include "plugin/wsseapi.h" +#include +#endif + class Group; #define SIGNAL_CAUSE "Signal" @@ -248,6 +255,20 @@ protected: }; protected: + + //ONVIF +#ifdef WITH_GSOAP + struct soap *soap; + bool ONVIF_Trigger_State; + bool ONVIF_Healthy; + _tev__CreatePullPointSubscription request; + _tev__CreatePullPointSubscriptionResponse response; + _tev__PullMessages tev__PullMessages; + _tev__PullMessagesResponse tev__PullMessagesResponse; + PullPointSubscriptionBindingProxy proxyEvent; + void set_credentials(struct soap *soap); +#endif + // These are read from the DB and thereafter remain unchanged unsigned int id; std::string name; @@ -272,6 +293,7 @@ protected: std::string onvif_username; std::string onvif_password; std::string onvif_options; + bool onvif_event_listener; std::string device; int palette; @@ -394,6 +416,7 @@ protected: VideoStore *videoStore; PacketQueue packetqueue; + std::unique_ptr Poller; packetqueue_iterator *analysis_it; std::unique_ptr analysis_thread; packetqueue_iterator *decoder_it; @@ -600,6 +623,7 @@ public: bool CheckSignal( const Image *image ); bool Analyse(); bool Decode(); + bool Poll(); void DumpImage( Image *dump_image ) const; void TimestampImage(Image *ts_image, SystemTimePoint ts_time) const; Event *openEvent( diff --git a/src/zm_poll_thread.cpp b/src/zm_poll_thread.cpp new file mode 100644 index 000000000..ee46dbfe5 --- /dev/null +++ b/src/zm_poll_thread.cpp @@ -0,0 +1,32 @@ +#include "zm_poll_thread.h" + +#include "zm_monitor.h" +#include "zm_signal.h" +#include "zm_time.h" + +PollThread::PollThread(Monitor *monitor) : + monitor_(monitor), terminate_(false) { + thread_ = std::thread(&PollThread::Run, this); +} + +PollThread::~PollThread() { + Stop(); +} + +void PollThread::Start() { + if (thread_.joinable()) thread_.join(); + terminate_ = false; + Debug(3, "Starting polling thread"); + thread_ = std::thread(&PollThread::Run, this); +} +void PollThread::Stop() { + terminate_ = true; + if (thread_.joinable()) { + thread_.join(); + } +} +void PollThread::Run() { + while (!(terminate_ or zm_terminate)) { + monitor_->Poll(); + } +} diff --git a/src/zm_poll_thread.h b/src/zm_poll_thread.h new file mode 100644 index 000000000..16444a951 --- /dev/null +++ b/src/zm_poll_thread.h @@ -0,0 +1,29 @@ +#ifndef ZM_POLL_THREAD_H +#define ZM_POLL_THREAD_H + +#include +#include +#include + +class Monitor; + +class PollThread { + public: + explicit PollThread(Monitor *monitor); + ~PollThread(); + PollThread(PollThread &rhs) = delete; + PollThread(PollThread &&rhs) = delete; + + void Start(); + void Stop(); + bool Stopped() const { return terminate_; } + + private: + void Run(); + + Monitor *monitor_; + std::atomic terminate_; + std::thread thread_; +}; + +#endif diff --git a/web/includes/Monitor.php b/web/includes/Monitor.php index b5e2ef853..b57fc2748 100644 --- a/web/includes/Monitor.php +++ b/web/includes/Monitor.php @@ -63,6 +63,7 @@ class Monitor extends ZM_Object { 'ONVIF_Username' => '', 'ONVIF_Password' => '', 'ONVIF_Options' => '', + 'ONVIF_Event_Listener' => '0', 'Device' => '', 'Channel' => 0, 'Format' => '0', diff --git a/web/skins/classic/views/monitor.php b/web/skins/classic/views/monitor.php index bfb0638c9..4c9ce3810 100644 --- a/web/skins/classic/views/monitor.php +++ b/web/skins/classic/views/monitor.php @@ -706,6 +706,10 @@ if (count($available_monitor_ids)) { + + + translate('Enabled'), '0'=>'Disabled'), $monitor->ONVIF_Event_Listener()); ?> +