Merge branch 'master' of github.com:ZoneMinder/zoneminder
commit
62e0d87a6b
|
@ -38,6 +38,7 @@ set(ZM_BIN_SRC_FILES
|
||||||
zm_monitor_rtsp2web.cpp
|
zm_monitor_rtsp2web.cpp
|
||||||
zm_monitor_janus.cpp
|
zm_monitor_janus.cpp
|
||||||
zm_monitor_amcrest.cpp
|
zm_monitor_amcrest.cpp
|
||||||
|
zm_monitor_onvif.cpp
|
||||||
zm_monitorlink_expression.cpp
|
zm_monitorlink_expression.cpp
|
||||||
#zm_monitorlink_token.cpp
|
#zm_monitorlink_token.cpp
|
||||||
zm_monitorstream.cpp
|
zm_monitorstream.cpp
|
||||||
|
|
|
@ -322,9 +322,7 @@ Monitor::Monitor() :
|
||||||
RTSP2Web_Manager(nullptr),
|
RTSP2Web_Manager(nullptr),
|
||||||
Janus_Manager(nullptr),
|
Janus_Manager(nullptr),
|
||||||
Amcrest_Manager(nullptr),
|
Amcrest_Manager(nullptr),
|
||||||
#ifdef WITH_GSOAP
|
onvif(nullptr),
|
||||||
soap(nullptr),
|
|
||||||
#endif
|
|
||||||
red_val(0),
|
red_val(0),
|
||||||
green_val(0),
|
green_val(0),
|
||||||
blue_val(0),
|
blue_val(0),
|
||||||
|
@ -1180,97 +1178,7 @@ bool Monitor::connect() {
|
||||||
if (use_Amcrest_API) {
|
if (use_Amcrest_API) {
|
||||||
Amcrest_Manager = new AmcrestAPI(this);
|
Amcrest_Manager = new AmcrestAPI(this);
|
||||||
} else { //using GSOAP
|
} else { //using GSOAP
|
||||||
#ifdef WITH_GSOAP
|
onvif = new ONVIF(this);
|
||||||
tev__PullMessages.Timeout = "PT20S";
|
|
||||||
tev__PullMessages.MessageLimit = 10;
|
|
||||||
std::string Termination_time = "PT60S";
|
|
||||||
wsnt__Renew.TerminationTime = &Termination_time;
|
|
||||||
soap = soap_new();
|
|
||||||
soap->connect_timeout = 0;
|
|
||||||
soap->recv_timeout = 0;
|
|
||||||
soap->send_timeout = 0;
|
|
||||||
//soap->bind_flags |= SO_REUSEADDR;
|
|
||||||
soap_register_plugin(soap, soap_wsse);
|
|
||||||
if (soap_wsa_compl) {soap_register_plugin(soap, soap_wsa);};
|
|
||||||
proxyEvent = PullPointSubscriptionBindingProxy(soap);
|
|
||||||
|
|
||||||
if (!onvif_url.empty()) {
|
|
||||||
std::string full_url = onvif_url + onvif_events_path;
|
|
||||||
proxyEvent.soap_endpoint = full_url.c_str();
|
|
||||||
set_credentials(soap);
|
|
||||||
const char *RequestMessageID = soap_wsa_compl ? soap_wsa_rand_uuid(soap) : "RequestMessageID";
|
|
||||||
if ((!soap_wsa_compl) || (soap_wsa_request(soap, RequestMessageID, proxyEvent.soap_endpoint, "CreatePullPointSubscriptionRequest") == SOAP_OK)) {
|
|
||||||
Debug(1, "ONVIF Endpoint: %s", proxyEvent.soap_endpoint);
|
|
||||||
int rc = proxyEvent.CreatePullPointSubscription(&request, response);
|
|
||||||
|
|
||||||
if (rc != SOAP_OK) {
|
|
||||||
const char *detail = soap_fault_detail(soap);
|
|
||||||
Error("ONVIF Couldn't create subscription! %d, fault:%s, detail:%s", rc, soap_fault_string(soap), detail ? detail : "null");
|
|
||||||
_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;
|
|
||||||
} else {
|
|
||||||
//Empty the stored messages
|
|
||||||
set_credentials(soap);
|
|
||||||
RequestMessageID = soap_wsa_compl ? soap_wsa_rand_uuid(soap):NULL;
|
|
||||||
if ((!soap_wsa_compl) || (soap_wsa_request(soap, RequestMessageID, response.SubscriptionReference.Address, "PullMessageRequest") == SOAP_OK)) {
|
|
||||||
Debug(1, "ONVIF :soap_wsa_request OK ");
|
|
||||||
if ((proxyEvent.PullMessages(response.SubscriptionReference.Address, NULL, &tev__PullMessages, tev__PullMessagesResponse) != SOAP_OK) &&
|
|
||||||
(soap->error != SOAP_EOF)
|
|
||||||
) { //SOAP_EOF could indicate no messages to pull.
|
|
||||||
Error("Couldn't do initial event pull! Error %i %s, %s", soap->error, soap_fault_string(soap), soap_fault_detail(soap));
|
|
||||||
Event_Poller_Healthy = false;
|
|
||||||
} else {
|
|
||||||
Debug(1, "Good Initial ONVIF Pull%i %s, %s", soap->error, soap_fault_string(soap), soap_fault_detail(soap));
|
|
||||||
Event_Poller_Healthy = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Error("ONVIF Couldn't set wsa headers RequestMessageID= %s ; TO= %s ; Request= PullMessageRequest .... ! Error %i %s, %s",RequestMessageID, response.SubscriptionReference.Address, soap->error, soap_fault_string(soap), soap_fault_detail(soap));
|
|
||||||
Event_Poller_Healthy = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// we renew the current subscription .........
|
|
||||||
if (soap_wsa_compl) {
|
|
||||||
set_credentials(soap);
|
|
||||||
RequestMessageID = soap_wsa_rand_uuid(soap);
|
|
||||||
if (soap_wsa_request(soap, RequestMessageID, response.SubscriptionReference.Address, "RenewRequest") == SOAP_OK) {
|
|
||||||
Debug(1, "ONVIF :soap_wsa_request OK");
|
|
||||||
if (proxyEvent.Renew(response.SubscriptionReference.Address, NULL, &wsnt__Renew, wsnt__RenewResponse) != SOAP_OK) {
|
|
||||||
Error("ONVIF Couldn't do initial Renew ! Error %i %s, %s", soap->error, soap_fault_string(soap), soap_fault_detail(soap));
|
|
||||||
if (soap->error==12) {//ActionNotSupported
|
|
||||||
Event_Poller_Healthy = true;
|
|
||||||
} else {
|
|
||||||
Event_Poller_Healthy = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Debug(1, "Good Initial ONVIF Renew %i %s, %s", soap->error, soap_fault_string(soap), soap_fault_detail(soap));
|
|
||||||
Event_Poller_Healthy = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Error("ONVIF Couldn't set wsa headers RequestMessageID=%s; TO=%s; Request=RenewRequest Error %i %s, %s",
|
|
||||||
RequestMessageID,
|
|
||||||
response.SubscriptionReference.Address,
|
|
||||||
soap->error,
|
|
||||||
soap_fault_string(soap),
|
|
||||||
soap_fault_detail(soap));
|
|
||||||
Event_Poller_Healthy = false;
|
|
||||||
}// end renew
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Error("ONVIF Couldn't set wsa headers RequestMessageID=%s; TO=%s; Request=CreatePullPointSubscriptionRequest Error %i %s, %s",
|
|
||||||
RequestMessageID, proxyEvent.soap_endpoint, soap->error, soap_fault_string(soap), soap_fault_detail(soap));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Warning("You must specify the url to the ONVIF endpoint");
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
Error("zmc not compiled with GSOAP. ONVIF support not built in!");
|
|
||||||
#endif
|
|
||||||
} // end if Armcrest or GSOAP
|
} // end if Armcrest or GSOAP
|
||||||
} else {
|
} else {
|
||||||
Debug(1, "Not Starting ONVIF");
|
Debug(1, "Not Starting ONVIF");
|
||||||
|
@ -1385,8 +1293,8 @@ Monitor::~Monitor() {
|
||||||
}
|
}
|
||||||
if (Amcrest_Manager != nullptr) {
|
if (Amcrest_Manager != nullptr) {
|
||||||
delete Amcrest_Manager;
|
delete Amcrest_Manager;
|
||||||
} else {
|
|
||||||
}
|
}
|
||||||
|
if (onvif) delete onvif;
|
||||||
} // end Monitor::~Monitor()
|
} // end Monitor::~Monitor()
|
||||||
|
|
||||||
void Monitor::AddPrivacyBitmask() {
|
void Monitor::AddPrivacyBitmask() {
|
||||||
|
@ -1962,92 +1870,22 @@ 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.
|
// 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();
|
std::chrono::system_clock::time_point loop_start_time = std::chrono::system_clock::now();
|
||||||
|
|
||||||
if (Event_Poller_Healthy) {
|
if (use_Amcrest_API) {
|
||||||
if (use_Amcrest_API) {
|
if (Event_Poller_Healthy) {
|
||||||
Amcrest_Manager->WaitForMessage();
|
Amcrest_Manager->WaitForMessage();
|
||||||
} else {
|
} else {
|
||||||
#ifdef WITH_GSOAP
|
delete Amcrest_Manager;
|
||||||
set_credentials(soap);
|
Amcrest_Manager = new AmcrestAPI(this);
|
||||||
const char *RequestMessageID = soap_wsa_compl ? soap_wsa_rand_uuid(soap) : "RequestMessageID";
|
}
|
||||||
if ((!soap_wsa_compl) || (soap_wsa_request(soap, RequestMessageID, response.SubscriptionReference.Address, "PullMessageRequest") == SOAP_OK)) {
|
} else if (onvif) {
|
||||||
Debug(1, ":soap_wsa_request OK; starting ONVIF PullMessageRequest ...");
|
if (onvif->isHealthy()) {
|
||||||
int result = proxyEvent.PullMessages(response.SubscriptionReference.Address, NULL, &tev__PullMessages, tev__PullMessagesResponse);
|
onvif->WaitForMessage();
|
||||||
if (result != SOAP_OK) {
|
} else {
|
||||||
const char *detail = soap_fault_detail(soap);
|
delete onvif;
|
||||||
Debug(1, "Result of getting ONVIF messages= %d soap_fault_string=%s detail=%s",
|
onvif = new ONVIF(this);
|
||||||
result, soap_fault_string(soap), detail ? detail : "null");
|
onvif->start();
|
||||||
if (result != SOAP_EOF) { //Ignore the timeout error
|
}
|
||||||
Error("Failed to get ONVIF messages! %d %s", result, soap_fault_string(soap));
|
} // end if Amcrest or not
|
||||||
// Event_Poller_Healthy = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Debug(1, "ONVIF polling : Got Good Response! %i", result);
|
|
||||||
for (auto msg : tev__PullMessagesResponse.wsnt__NotificationMessage) {
|
|
||||||
if ((msg->Topic != nullptr) &&
|
|
||||||
(msg->Topic->__any.text != nullptr) &&
|
|
||||||
std::strstr(msg->Topic->__any.text, onvif_alarm_txt.c_str()) &&
|
|
||||||
(msg->Message.__any.elts != nullptr) &&
|
|
||||||
(msg->Message.__any.elts->next != nullptr) &&
|
|
||||||
(msg->Message.__any.elts->next->elts != nullptr) &&
|
|
||||||
(msg->Message.__any.elts->next->elts->atts != nullptr) &&
|
|
||||||
(msg->Message.__any.elts->next->elts->atts->next != nullptr) &&
|
|
||||||
(msg->Message.__any.elts->next->elts->atts->next->text != nullptr)
|
|
||||||
) {
|
|
||||||
Info("Got Motion Alarm!");
|
|
||||||
if (strcmp(msg->Message.__any.elts->next->elts->atts->next->text, "true") == 0) {
|
|
||||||
//Event Start
|
|
||||||
Info("Triggered on ONVIF");
|
|
||||||
if (!Poll_Trigger_State) {
|
|
||||||
Info("Triggered Event");
|
|
||||||
Poll_Trigger_State = true;
|
|
||||||
std::this_thread::sleep_for(std::chrono::seconds(1)); //thread sleep
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Info("Triggered off ONVIF");
|
|
||||||
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;
|
|
||||||
Info("Setting ClosesEvent");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Debug(1, "Got a message that we couldn't parse");
|
|
||||||
}
|
|
||||||
} // end foreach msg
|
|
||||||
|
|
||||||
// we renew the current subscription .........
|
|
||||||
if (soap_wsa_compl) {
|
|
||||||
set_credentials(soap);
|
|
||||||
std::string Termination_time = "PT60S";
|
|
||||||
wsnt__Renew.TerminationTime = &Termination_time;
|
|
||||||
RequestMessageID = soap_wsa_compl ? soap_wsa_rand_uuid(soap) : "RequestMessageID";
|
|
||||||
if ((!soap_wsa_compl) || (soap_wsa_request(soap, RequestMessageID, response.SubscriptionReference.Address, "RenewRequest") == SOAP_OK)) {
|
|
||||||
Debug(1, ":soap_wsa_request OK");
|
|
||||||
if (proxyEvent.Renew(response.SubscriptionReference.Address, NULL, &wsnt__Renew, wsnt__RenewResponse) != SOAP_OK) {
|
|
||||||
Error("Couldn't do Renew! Error %i %s, %s", soap->error, soap_fault_string(soap), soap_fault_detail(soap));
|
|
||||||
if (soap->error==12) {//ActionNotSupported
|
|
||||||
Event_Poller_Healthy = true;
|
|
||||||
} else {
|
|
||||||
Event_Poller_Healthy = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Debug(1, "Good Renew ONVIF Renew %i %s, %s", soap->error, soap_fault_string(soap), soap_fault_detail(soap));
|
|
||||||
Event_Poller_Healthy = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Error("Couldn't set wsa headers RequestMessageID=%s; TO=%s; Request= RenewRequest .... ! Error %i %s, %s",
|
|
||||||
RequestMessageID, response.SubscriptionReference.Address, soap->error, soap_fault_string(soap), soap_fault_detail(soap));
|
|
||||||
Event_Poller_Healthy = false;
|
|
||||||
} // end renew
|
|
||||||
}
|
|
||||||
} // end if SOAP OK/NOT OK
|
|
||||||
} else {
|
|
||||||
Error("Couldn't set wsa headers RequestMessageID= %s ; TO= %s ; Request= PullMessageRequest .... ! Error %i %s, %s",
|
|
||||||
RequestMessageID, response.SubscriptionReference.Address, soap->error, soap_fault_string(soap), soap_fault_detail(soap));
|
|
||||||
} // end if soap == OK
|
|
||||||
#endif
|
|
||||||
} // end if Amcrest or not
|
|
||||||
} // end if Healthy
|
|
||||||
|
|
||||||
if (RTSP2Web_enabled and RTSP2Web_Manager) {
|
if (RTSP2Web_enabled and RTSP2Web_Manager) {
|
||||||
Debug(1, "Trying to check RTSP2Web in Poller");
|
Debug(1, "Trying to check RTSP2Web in Poller");
|
||||||
|
@ -2113,14 +1951,14 @@ bool Monitor::Analyse() {
|
||||||
|
|
||||||
#ifdef WITH_GSOAP
|
#ifdef WITH_GSOAP
|
||||||
if (onvif_event_listener && Event_Poller_Healthy) {
|
if (onvif_event_listener && Event_Poller_Healthy) {
|
||||||
if (Poll_Trigger_State) {
|
if ((onvif and onvif->isAlarmed()) or Poll_Trigger_State) {
|
||||||
score += 9;
|
score += 9;
|
||||||
Debug(4, "Triggered on ONVIF");
|
Debug(4, "Triggered on ONVIF");
|
||||||
Event::StringSet noteSet;
|
Event::StringSet noteSet;
|
||||||
noteSet.insert("ONVIF2");
|
noteSet.insert("ONVIF");
|
||||||
noteSetMap[MOTION_CAUSE] = noteSet;
|
noteSetMap[MOTION_CAUSE] = noteSet;
|
||||||
cause += "ONVIF";
|
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 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 (!Event_Poller_Closes_Event && state == ALARM)
|
if (!Event_Poller_Closes_Event && state == ALARM)
|
||||||
Poll_Trigger_State = false;
|
Poll_Trigger_State = false;
|
||||||
} // end ONVIF_Trigger
|
} // end ONVIF_Trigger
|
||||||
|
@ -3597,6 +3435,7 @@ int Monitor::Play() {
|
||||||
return 1;
|
return 1;
|
||||||
} // end int Monitor::Play()
|
} // end int Monitor::Play()
|
||||||
|
|
||||||
|
// Close can be called multiple times, for example before object delete, and then in the destructor.
|
||||||
int Monitor::Close() {
|
int Monitor::Close() {
|
||||||
Pause();
|
Pause();
|
||||||
|
|
||||||
|
@ -3605,25 +3444,11 @@ int Monitor::Close() {
|
||||||
Poller->Stop();
|
Poller->Stop();
|
||||||
Debug(1, "Poller stopped");
|
Debug(1, "Poller stopped");
|
||||||
}
|
}
|
||||||
#ifdef WITH_GSOAP
|
if (onvif) {
|
||||||
if (onvif_event_listener && (soap != nullptr)) {
|
delete onvif;
|
||||||
Debug(1, "Tearing Down Onvif");
|
onvif = nullptr;
|
||||||
_wsnt__Unsubscribe wsnt__Unsubscribe;
|
}
|
||||||
_wsnt__UnsubscribeResponse wsnt__UnsubscribeResponse;
|
|
||||||
const char *RequestMessageID = soap_wsa_compl ? soap_wsa_rand_uuid(soap) : "RequestMessageID";
|
|
||||||
if ((!soap_wsa_compl) || (soap_wsa_request(soap, RequestMessageID, response.SubscriptionReference.Address, "UnsubscribeRequest") == SOAP_OK)) {
|
|
||||||
proxyEvent.Unsubscribe(response.SubscriptionReference.Address, NULL, &wsnt__Unsubscribe, wsnt__UnsubscribeResponse);
|
|
||||||
} else {
|
|
||||||
Error("Couldn't set wsa headers RequestMessageID=%s; TO= %s; Request=UnsubscribeRequest .... ! Error %i %s, %s",
|
|
||||||
RequestMessageID, response.SubscriptionReference.Address, soap->error, soap_fault_string(soap), soap_fault_detail(soap));
|
|
||||||
}
|
|
||||||
|
|
||||||
soap_destroy(soap);
|
|
||||||
soap_end(soap);
|
|
||||||
soap_free(soap);
|
|
||||||
soap = nullptr;
|
|
||||||
} //End ONVIF
|
|
||||||
#endif
|
|
||||||
// RTSP2Web teardown
|
// RTSP2Web teardown
|
||||||
if (RTSP2Web_enabled and (purpose == CAPTURE) and RTSP2Web_Manager) {
|
if (RTSP2Web_enabled and (purpose == CAPTURE) and RTSP2Web_Manager) {
|
||||||
delete RTSP2Web_Manager;
|
delete RTSP2Web_Manager;
|
||||||
|
@ -3732,33 +3557,3 @@ StringVector Monitor::GroupNames() {
|
||||||
return groupnames;
|
return groupnames;
|
||||||
} // end Monitor::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
|
|
||||||
|
|
|
@ -321,6 +321,31 @@ class Monitor : public std::enable_shared_from_this<Monitor> {
|
||||||
};
|
};
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
|
class ONVIF {
|
||||||
|
protected:
|
||||||
|
Monitor *parent;
|
||||||
|
bool alarmed;
|
||||||
|
bool healthy;
|
||||||
|
#ifdef WITH_GSOAP
|
||||||
|
struct soap *soap = nullptr;
|
||||||
|
_tev__CreatePullPointSubscription request;
|
||||||
|
_tev__CreatePullPointSubscriptionResponse response;
|
||||||
|
_tev__PullMessages tev__PullMessages;
|
||||||
|
_tev__PullMessagesResponse tev__PullMessagesResponse;
|
||||||
|
_wsnt__Renew wsnt__Renew;
|
||||||
|
_wsnt__RenewResponse wsnt__RenewResponse;
|
||||||
|
PullPointSubscriptionBindingProxy proxyEvent;
|
||||||
|
void set_credentials(struct soap *soap);
|
||||||
|
#endif
|
||||||
|
public:
|
||||||
|
explicit ONVIF(Monitor *parent_);
|
||||||
|
~ONVIF();
|
||||||
|
void start();
|
||||||
|
void WaitForMessage();
|
||||||
|
bool isAlarmed() const { return alarmed; };
|
||||||
|
bool isHealthy() const { return healthy; };
|
||||||
|
};
|
||||||
|
|
||||||
class AmcrestAPI {
|
class AmcrestAPI {
|
||||||
protected:
|
protected:
|
||||||
Monitor *parent;
|
Monitor *parent;
|
||||||
|
@ -613,19 +638,7 @@ class Monitor : public std::enable_shared_from_this<Monitor> {
|
||||||
RTSP2WebManager *RTSP2Web_Manager;
|
RTSP2WebManager *RTSP2Web_Manager;
|
||||||
JanusManager *Janus_Manager;
|
JanusManager *Janus_Manager;
|
||||||
AmcrestAPI *Amcrest_Manager;
|
AmcrestAPI *Amcrest_Manager;
|
||||||
|
ONVIF *onvif;
|
||||||
#ifdef WITH_GSOAP
|
|
||||||
struct soap *soap = nullptr;
|
|
||||||
_tev__CreatePullPointSubscription request;
|
|
||||||
_tev__CreatePullPointSubscriptionResponse response;
|
|
||||||
_tev__PullMessages tev__PullMessages;
|
|
||||||
_tev__PullMessagesResponse tev__PullMessagesResponse;
|
|
||||||
_wsnt__Renew wsnt__Renew;
|
|
||||||
_wsnt__RenewResponse wsnt__RenewResponse;
|
|
||||||
PullPointSubscriptionBindingProxy proxyEvent;
|
|
||||||
void set_credentials(struct soap *soap);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
// Used in check signal
|
// Used in check signal
|
||||||
uint8_t red_val;
|
uint8_t red_val;
|
||||||
|
|
|
@ -0,0 +1,268 @@
|
||||||
|
//
|
||||||
|
// ZoneMinder Monitor::ONVIF Class Implementation
|
||||||
|
// Copyright (C) 2024 ZoneMinder Inc
|
||||||
|
//
|
||||||
|
// 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 <cstring>
|
||||||
|
|
||||||
|
Monitor::ONVIF::ONVIF(Monitor *parent_) :
|
||||||
|
parent(parent_),
|
||||||
|
alarmed(false),
|
||||||
|
healthy(false),
|
||||||
|
#ifdef WITH_GSOAP
|
||||||
|
soap(nullptr)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Monitor::ONVIF::~ONVIF() {
|
||||||
|
#ifdef WITH_GSOAP
|
||||||
|
if (soap != nullptr) {
|
||||||
|
Debug(1, "Tearing Down Onvif");
|
||||||
|
_wsnt__Unsubscribe wsnt__Unsubscribe;
|
||||||
|
_wsnt__UnsubscribeResponse wsnt__UnsubscribeResponse;
|
||||||
|
const char *RequestMessageID = parent->soap_wsa_compl ? soap_wsa_rand_uuid(soap) : "RequestMessageID";
|
||||||
|
if ((!parent->soap_wsa_compl) || (soap_wsa_request(soap, RequestMessageID, response.SubscriptionReference.Address, "UnsubscribeRequest") == SOAP_OK)) {
|
||||||
|
proxyEvent.Unsubscribe(response.SubscriptionReference.Address, nullptr, &wsnt__Unsubscribe, wsnt__UnsubscribeResponse);
|
||||||
|
} else {
|
||||||
|
Error("Couldn't set wsa headers RequestMessageID=%s; TO= %s; Request=UnsubscribeRequest .... ! Error %i %s, %s",
|
||||||
|
RequestMessageID, response.SubscriptionReference.Address, soap->error, soap_fault_string(soap), soap_fault_detail(soap));
|
||||||
|
}
|
||||||
|
|
||||||
|
soap_destroy(soap);
|
||||||
|
soap_end(soap);
|
||||||
|
soap_free(soap);
|
||||||
|
soap = nullptr;
|
||||||
|
} // end if soap
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Monitor::ONVIF::start() {
|
||||||
|
#ifdef WITH_GSOAP
|
||||||
|
tev__PullMessages.Timeout = "PT20S";
|
||||||
|
tev__PullMessages.MessageLimit = 10;
|
||||||
|
std::string Termination_time = "PT60S";
|
||||||
|
wsnt__Renew.TerminationTime = &Termination_time;
|
||||||
|
soap = soap_new();
|
||||||
|
soap->connect_timeout = 0;
|
||||||
|
soap->recv_timeout = 0;
|
||||||
|
soap->send_timeout = 0;
|
||||||
|
//soap->bind_flags |= SO_REUSEADDR;
|
||||||
|
soap_register_plugin(soap, soap_wsse);
|
||||||
|
if (parent->soap_wsa_compl) {soap_register_plugin(soap, soap_wsa);};
|
||||||
|
proxyEvent = PullPointSubscriptionBindingProxy(soap);
|
||||||
|
|
||||||
|
if (!parent->onvif_url.empty()) {
|
||||||
|
std::string full_url = parent->onvif_url + parent->onvif_events_path;
|
||||||
|
proxyEvent.soap_endpoint = full_url.c_str();
|
||||||
|
set_credentials(soap);
|
||||||
|
const char *RequestMessageID = parent->soap_wsa_compl ? soap_wsa_rand_uuid(soap) : "RequestMessageID";
|
||||||
|
if ((!parent->soap_wsa_compl) || (soap_wsa_request(soap, RequestMessageID, proxyEvent.soap_endpoint, "CreatePullPointSubscriptionRequest") == SOAP_OK)) {
|
||||||
|
Debug(1, "ONVIF Endpoint: %s", proxyEvent.soap_endpoint);
|
||||||
|
int rc = proxyEvent.CreatePullPointSubscription(&request, response);
|
||||||
|
|
||||||
|
if (rc != SOAP_OK) {
|
||||||
|
const char *detail = soap_fault_detail(soap);
|
||||||
|
Error("ONVIF Couldn't create subscription! %d, fault:%s, detail:%s", rc, soap_fault_string(soap), detail ? detail : "null");
|
||||||
|
_wsnt__Unsubscribe wsnt__Unsubscribe;
|
||||||
|
_wsnt__UnsubscribeResponse wsnt__UnsubscribeResponse;
|
||||||
|
proxyEvent.Unsubscribe(response.SubscriptionReference.Address, nullptr, &wsnt__Unsubscribe, wsnt__UnsubscribeResponse);
|
||||||
|
soap_destroy(soap);
|
||||||
|
soap_end(soap);
|
||||||
|
soap_free(soap);
|
||||||
|
soap = nullptr;
|
||||||
|
} else {
|
||||||
|
//Empty the stored messages
|
||||||
|
set_credentials(soap);
|
||||||
|
|
||||||
|
RequestMessageID = parent->soap_wsa_compl ? soap_wsa_rand_uuid(soap):nullptr;
|
||||||
|
if ((!parent->soap_wsa_compl) || (soap_wsa_request(soap, RequestMessageID, response.SubscriptionReference.Address, "PullMessageRequest") == SOAP_OK)) {
|
||||||
|
Debug(1, "ONVIF :soap_wsa_request OK ");
|
||||||
|
if ((proxyEvent.PullMessages(response.SubscriptionReference.Address, nullptr, &tev__PullMessages, tev__PullMessagesResponse) != SOAP_OK) &&
|
||||||
|
(soap->error != SOAP_EOF)
|
||||||
|
) { //SOAP_EOF could indicate no messages to pull.
|
||||||
|
Error("Couldn't do initial event pull! Error %i %s, %s", soap->error, soap_fault_string(soap), soap_fault_detail(soap));
|
||||||
|
healthy = false;
|
||||||
|
} else {
|
||||||
|
Debug(1, "Good Initial ONVIF Pull%i %s, %s", soap->error, soap_fault_string(soap), soap_fault_detail(soap));
|
||||||
|
healthy = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Error("ONVIF Couldn't set wsa headers RequestMessageID= %s ; TO= %s ; Request= PullMessageRequest .... ! Error %i %s, %s",RequestMessageID, response.SubscriptionReference.Address, soap->error, soap_fault_string(soap), soap_fault_detail(soap));
|
||||||
|
healthy = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we renew the current subscription .........
|
||||||
|
if (parent->soap_wsa_compl) {
|
||||||
|
set_credentials(soap);
|
||||||
|
RequestMessageID = soap_wsa_rand_uuid(soap);
|
||||||
|
if (soap_wsa_request(soap, RequestMessageID, response.SubscriptionReference.Address, "RenewRequest") == SOAP_OK) {
|
||||||
|
Debug(1, "ONVIF :soap_wsa_request OK");
|
||||||
|
if (proxyEvent.Renew(response.SubscriptionReference.Address, nullptr, &wsnt__Renew, wsnt__RenewResponse) != SOAP_OK) {
|
||||||
|
Error("ONVIF Couldn't do initial Renew ! Error %i %s, %s", soap->error, soap_fault_string(soap), soap_fault_detail(soap));
|
||||||
|
if (soap->error==12) {//ActionNotSupported
|
||||||
|
healthy = true;
|
||||||
|
} else {
|
||||||
|
healthy = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Debug(1, "Good Initial ONVIF Renew %i %s, %s", soap->error, soap_fault_string(soap), soap_fault_detail(soap));
|
||||||
|
healthy = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Error("ONVIF Couldn't set wsa headers RequestMessageID=%s; TO=%s; Request=RenewRequest Error %i %s, %s",
|
||||||
|
RequestMessageID,
|
||||||
|
response.SubscriptionReference.Address,
|
||||||
|
soap->error,
|
||||||
|
soap_fault_string(soap),
|
||||||
|
soap_fault_detail(soap));
|
||||||
|
healthy = false;
|
||||||
|
} // end renew
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Error("ONVIF Couldn't set wsa headers RequestMessageID=%s; TO=%s; Request=CreatePullPointSubscriptionRequest Error %i %s, %s",
|
||||||
|
RequestMessageID, proxyEvent.soap_endpoint, soap->error, soap_fault_string(soap), soap_fault_detail(soap));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Warning("You must specify the url to the ONVIF endpoint");
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
Error("zmc not compiled with GSOAP. ONVIF support not built in!");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Monitor::ONVIF::WaitForMessage() {
|
||||||
|
#ifdef WITH_GSOAP
|
||||||
|
set_credentials(soap);
|
||||||
|
const char *RequestMessageID = parent->soap_wsa_compl ? soap_wsa_rand_uuid(soap) : "RequestMessageID";
|
||||||
|
if ((!parent->soap_wsa_compl) || (soap_wsa_request(soap, RequestMessageID, response.SubscriptionReference.Address, "PullMessageRequest") == SOAP_OK)) {
|
||||||
|
Debug(1, ":soap_wsa_request OK; starting ONVIF PullMessageRequest ...");
|
||||||
|
int result = proxyEvent.PullMessages(response.SubscriptionReference.Address, nullptr, &tev__PullMessages, tev__PullMessagesResponse);
|
||||||
|
if (result != SOAP_OK) {
|
||||||
|
const char *detail = soap_fault_detail(soap);
|
||||||
|
Debug(1, "Result of getting ONVIF messages= %d soap_fault_string=%s detail=%s",
|
||||||
|
result, soap_fault_string(soap), detail ? detail : "null");
|
||||||
|
if (result != SOAP_EOF) { //Ignore the timeout error
|
||||||
|
Error("Failed to get ONVIF messages! %d %s", result, soap_fault_string(soap));
|
||||||
|
// healthy = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Debug(1, "ONVIF polling : Got Good Response! %i", result);
|
||||||
|
for (auto msg : tev__PullMessagesResponse.wsnt__NotificationMessage) {
|
||||||
|
if ((msg->Topic != nullptr) &&
|
||||||
|
(msg->Topic->__any.text != nullptr) &&
|
||||||
|
std::strstr(msg->Topic->__any.text, parent->onvif_alarm_txt.c_str()) &&
|
||||||
|
(msg->Message.__any.elts != nullptr) &&
|
||||||
|
(msg->Message.__any.elts->next != nullptr) &&
|
||||||
|
(msg->Message.__any.elts->next->elts != nullptr) &&
|
||||||
|
(msg->Message.__any.elts->next->elts->atts != nullptr) &&
|
||||||
|
(msg->Message.__any.elts->next->elts->atts->next != nullptr) &&
|
||||||
|
(msg->Message.__any.elts->next->elts->atts->next->text != nullptr)
|
||||||
|
) {
|
||||||
|
Info("Got Motion Alarm!");
|
||||||
|
if (strcmp(msg->Message.__any.elts->next->elts->atts->next->text, "true") == 0) {
|
||||||
|
// Event Start
|
||||||
|
Info("Triggered on ONVIF");
|
||||||
|
if (!alarmed) {
|
||||||
|
Info("Triggered Event");
|
||||||
|
alarmed = true;
|
||||||
|
// Why sleep?
|
||||||
|
std::this_thread::sleep_for(std::chrono::seconds(1)); //thread sleep
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Info("Triggered off ONVIF");
|
||||||
|
alarmed = 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;
|
||||||
|
Info("Setting ClosesEvent");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Debug(1, "Got a message that we couldn't parse");
|
||||||
|
if ((msg->Topic != nullptr) && (msg->Topic->__any.text != nullptr)) {
|
||||||
|
Debug(1, "text was %s", msg->Topic->__any.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // end foreach msg
|
||||||
|
|
||||||
|
// we renew the current subscription .........
|
||||||
|
if (parent->soap_wsa_compl) {
|
||||||
|
set_credentials(soap);
|
||||||
|
std::string Termination_time = "PT60S";
|
||||||
|
wsnt__Renew.TerminationTime = &Termination_time;
|
||||||
|
RequestMessageID = parent->soap_wsa_compl ? soap_wsa_rand_uuid(soap) : "RequestMessageID";
|
||||||
|
if ((!parent->soap_wsa_compl) || (soap_wsa_request(soap, RequestMessageID, response.SubscriptionReference.Address, "RenewRequest") == SOAP_OK)) {
|
||||||
|
Debug(1, ":soap_wsa_request OK");
|
||||||
|
if (proxyEvent.Renew(response.SubscriptionReference.Address, nullptr, &wsnt__Renew, wsnt__RenewResponse) != SOAP_OK) {
|
||||||
|
Error("Couldn't do Renew! Error %i %s, %s", soap->error, soap_fault_string(soap), soap_fault_detail(soap));
|
||||||
|
if (soap->error==12) {//ActionNotSupported
|
||||||
|
healthy = true;
|
||||||
|
} else {
|
||||||
|
healthy = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Debug(1, "Good Renew ONVIF Renew %i %s, %s", soap->error, soap_fault_string(soap), soap_fault_detail(soap));
|
||||||
|
healthy = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Error("Couldn't set wsa headers RequestMessageID=%s; TO=%s; Request= RenewRequest .... ! Error %i %s, %s",
|
||||||
|
RequestMessageID, response.SubscriptionReference.Address, soap->error, soap_fault_string(soap), soap_fault_detail(soap));
|
||||||
|
healthy = false;
|
||||||
|
} // end renew
|
||||||
|
}
|
||||||
|
} // end if SOAP OK/NOT OK
|
||||||
|
} else {
|
||||||
|
Error("Couldn't set wsa headers RequestMessageID= %s ; TO= %s ; Request= PullMessageRequest .... ! Error %i %s, %s",
|
||||||
|
RequestMessageID, response.SubscriptionReference.Address, soap->error, soap_fault_string(soap), soap_fault_detail(soap));
|
||||||
|
} // end if soap == OK
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef WITH_GSOAP
|
||||||
|
//ONVIF Set Credentials
|
||||||
|
void Monitor::ONVIF::set_credentials(struct soap *soap) {
|
||||||
|
soap_wsse_delete_Security(soap);
|
||||||
|
soap_wsse_add_Timestamp(soap, nullptr, 10);
|
||||||
|
soap_wsse_add_UsernameTokenDigest(soap, "Auth", parent->onvif_username.c_str(), parent->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
|
Loading…
Reference in New Issue