From c9db5f44a4a210e81e1da69b3c2db2d4f1b3f2e9 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Jan 2022 12:05:29 -0500 Subject: [PATCH 1/4] cherry pick Add check to prevent tight busy loop if ONVIF setup fails --- src/zm_monitor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 4feea00fe..958410efe 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1077,7 +1077,7 @@ bool Monitor::connect() { //ONVIF Setup #ifdef WITH_GSOAP ONVIF_Trigger_State = FALSE; - if (onvif_event_listener) { //Temporarily using this option to enable the feature + if (onvif_event_listener) { Debug(1, "Starting ONVIF"); ONVIF_Healthy = FALSE; if (onvif_options.find("closes_event") != std::string::npos) { //Option to indicate that ONVIF will send a close event message @@ -3130,7 +3130,7 @@ int Monitor::PrimeCapture() { #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 (onvif_event_listener && ONVIF_Healthy) { if (!Poller) { Poller = zm::make_unique(this); } else { From 8d061750242b35dc4429ed5cb69771d9cf83d301 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Jan 2022 23:07:48 -0500 Subject: [PATCH 2/4] Rework to use ZoneMinder::Monitor class. Simplify loadMonitors and get rid of loadMonitor. Add in ServerId change handling. --- scripts/zmtrigger.pl.in | 62 ++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 38 deletions(-) diff --git a/scripts/zmtrigger.pl.in b/scripts/zmtrigger.pl.in index 83fcf29f5..94f59e001 100644 --- a/scripts/zmtrigger.pl.in +++ b/scripts/zmtrigger.pl.in @@ -42,6 +42,7 @@ use constant SELECT_TIMEOUT => 0.25; @EXTRA_PERL_LIB@ use ZoneMinder; +use ZoneMinder::Monitor; use ZoneMinder::Trigger::Channel::Inet; use ZoneMinder::Trigger::Channel::Unix; use ZoneMinder::Trigger::Channel::Serial; @@ -203,14 +204,10 @@ while (!$zm_terminate) { # Check for alarms that might have happened my @out_messages; foreach my $monitor ( values %monitors ) { - if ($$monitor{Function} eq 'None') { - $monitor_reload_time = 0; - next; - } - - if (!zmMemVerify($monitor)) { + if (!$monitor->connect()) { # Our attempt to verify the memory handle failed. We should reload the monitors. # Don't need to zmMemInvalidate because the monitor reload will do it. + Debug("Failed connect, putting on reloads"); push @needsReload, $monitor; next; } @@ -249,6 +246,7 @@ while (!$zm_terminate) { } $monitor->{LastState} = $state; $monitor->{LastEvent} = $last_event; + $monitor->disconnect(); } # end foreach monitor foreach my $connection ( @out_connections ) { @@ -298,15 +296,22 @@ while (!$zm_terminate) { # Reload all monitors from the dB every MONITOR_RELOAD_INTERVAL if ( (time() - $monitor_reload_time) > MONITOR_RELOAD_INTERVAL ) { - foreach my $monitor ( values(%monitors) ) { - zmMemInvalidate( $monitor ); # Free up any used memory handle - } loadMonitors(); @needsReload = (); # We just reloaded all monitors so no need reload a specific monitor # If we have NOT just reloaded all monitors, reload a specific monitor if its shared mem changed - } elsif ( @needsReload ) { - foreach my $monitor ( @needsReload ) { - loadMonitor($monitor); + } elsif (@needsReload) { + foreach my $monitor (@needsReload) { + $monitor = $monitors{$monitor->Id()} = ZoneMinder::Monitor->find_one(Id=>$monitor->Id()); + if ( $$monitor{Function} eq 'None' ) { + delete $monitors{$monitor->Id()}; + } elsif ( $Config{ZM_SERVER_ID} and ($$monitor{ServerId} != $Config{ZM_SERVER_ID})) { + delete $monitors{$monitor->Id()}; + } else { + if ($monitor->connect()) { + $monitor->{LastState} = zmGetMonitorState($monitor); + $monitor->{LastEvent} = zmGetLastEvent($monitor); + } + } } @needsReload = (); } @@ -317,40 +322,21 @@ while (!$zm_terminate) { Info('Trigger daemon exiting'); exit; -sub loadMonitor { - my $monitor = shift; - - Debug('Loading monitor '.$monitor); - zmMemInvalidate($monitor); - - if ( zmMemVerify($monitor) ) { # This will re-init shared memory - $monitor->{LastState} = zmGetMonitorState($monitor); - $monitor->{LastEvent} = zmGetLastEvent($monitor); - } -} # end sub loadMonitor - sub loadMonitors { $monitor_reload_time = time(); - my %new_monitors = (); + %monitors = (); - my $sql = 'SELECT * FROM `Monitors` - WHERE find_in_set( `Function`, \'Modect,Mocord,Nodect,Record\' )'. - ( $Config{ZM_SERVER_ID} ? ' AND `ServerId`=?' : '' ) - ; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID} : () ) - or Fatal( "Can't execute: ".$sth->errstr() ); - - while ( my $monitor = $sth->fetchrow_hashref() ) { - if ( zmMemVerify($monitor) ) { # This will re-init shared memory + foreach my $monitor ( ZoneMinder::Monitor->find( + Function=>['Modect','Mocord','Nodect','Record'], + ($Config{ZM_SERVER_ID} ? (ServerId=>$Config{ZM_SERVER_ID}) : ()), + )) { + if ($monitor->connect()) { # This will re-init shared memory $monitor->{LastState} = zmGetMonitorState($monitor); $monitor->{LastEvent} = zmGetLastEvent($monitor); } - $new_monitors{$monitor->{Id}} = $monitor; + $monitors{$monitor->{Id}} = $monitor; } # end while fetchrow - %monitors = %new_monitors; } # end sub loadMonitors sub handleMessage { From e4693c251c598d92be0c54f54e8b41e69893fdb2 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Jan 2022 23:08:29 -0500 Subject: [PATCH 3/4] add backticks around field names because some like Function are reserved --- scripts/ZoneMinder/lib/ZoneMinder/Object.pm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Object.pm b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm index e54bb15ce..d64ee7e8a 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Object.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm @@ -639,9 +639,9 @@ $log->debug("Have array for $k $$search{$k}") if DEBUG_ALL; if ( ! ( $db_field =~ /\?/ ) ) { if ( @{$$search{$k}} != 1 ) { - push @where, $db_field .' IN ('.join(',', map {'?'} @{$$search{$k}} ) . ')'; + push @where, '`'.$db_field .'` IN ('.join(',', map {'?'} @{$$search{$k}} ) . ')'; } else { - push @where, $db_field.'=?'; + push @where, '`'.$db_field.'`=?'; } # end if } else { $log->debug("Have question ? for $k $$search{$k} $db_field") if DEBUG_ALL; @@ -656,10 +656,10 @@ $log->debug("Have question ? for $k $$search{$k} $db_field") if DEBUG_ALL; foreach my $p_k ( keys %{$$search{$k}} ) { my $v = $$search{$k}{$p_k}; if ( ref $v eq 'ARRAY' ) { - push @where, $db_field.' IN ('.join(',', map {'?'} @{$v} ) . ')'; + push @where, '`'.$db_field.'` IN ('.join(',', map {'?'} @{$v} ) . ')'; push @values, $p_k, @{$v}; } else { - push @where, $db_field.'=?'; + push @where, '`'.$db_field.'`=?'; push @values, $p_k, $v; } # end if } # end foreach p_k @@ -667,7 +667,7 @@ $log->debug("Have question ? for $k $$search{$k} $db_field") if DEBUG_ALL; push @where, $db_field.' IS NULL'; } else { if ( ! ( $db_field =~ /\?/ ) ) { - push @where, $db_field .'=?'; + push @where, '`'.$db_field .'`=?'; } else { push @where, $db_field; } From 3d042284857a69496b5404089546458494386b7e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Jan 2022 23:10:17 -0500 Subject: [PATCH 4/4] rework ::Analyse. The state engine will only go into alarm on actual motion, not on a skipped frame. TRIGGERS and ONVIF events will immediately create events. --- src/zm_monitor.cpp | 78 ++++++++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 958410efe..587c49f33 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1870,11 +1870,18 @@ bool Monitor::Analyse() { score += 9; Debug(1, "Triggered on ONVIF"); if (!event) { + Event::StringSet noteSet; + noteSet.insert("ONVIF2"); + noteSetMap[MOTION_CAUSE] = noteSet; + + event = openEvent(snap, "ONVIF", noteSetMap); cause += "ONVIF"; + } else { + event->addNote(MOTION_CAUSE, "ONVIF2"); +// Add to cause } - Event::StringSet noteSet; - noteSet.insert("ONVIF2"); - noteSetMap[MOTION_CAUSE] = noteSet; + // Regardless of previous state, we go to ALARM + shared_data->state = state = 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 (!ONVIF_Closes_Event && state == ALARM) ONVIF_Trigger_State = FALSE; @@ -1886,11 +1893,16 @@ bool Monitor::Analyse() { score += trigger_data->trigger_score; Debug(1, "Triggered on score += %d => %d", trigger_data->trigger_score, score); if (!event) { - cause += trigger_data->trigger_cause; + Event::StringSet noteSet; + noteSet.insert(trigger_data->trigger_text); + noteSetMap[trigger_data->trigger_cause] = noteSet; + event = openEvent(snap, trigger_data->trigger_cause, noteSetMap); + Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name.c_str(), analysis_image_count, event->Id()); + } else { + event->addNote(trigger_data->trigger_cause, trigger_data->trigger_text); + // Need to know if we should end the previous and start a new one, or just add the data } - Event::StringSet noteSet; - noteSet.insert(trigger_data->trigger_text); - noteSetMap[trigger_data->trigger_cause] = noteSet; + shared_data->state = state = ALARM; } // end if trigger_on // FIXME this snap might not be the one that caused the signal change. Need to store that in the packet. @@ -1967,8 +1979,6 @@ bool Monitor::Analyse() { } // end while ! decoded } // end if decoding enabled - SystemTimePoint timestamp = snap->timestamp; - if (Active() and (function == MODECT or function == MOCORD)) { Debug(3, "signal and active and modect"); Event::StringSet zoneSet; @@ -1990,7 +2000,7 @@ bool Monitor::Analyse() { } else if (!(analysis_image_count % (motion_frame_skip+1))) { Debug(1, "Detecting motion on image %d, image %p", snap->image_index, snap->image); // Get new score. - int motion_score = DetectMotion(*(snap->image), zoneSet); + snap->score = DetectMotion(*(snap->image), zoneSet); // lets construct alarm cause. It will contain cause + names of zones alarmed snap->zone_stats.reserve(zones.size()); @@ -2004,11 +2014,11 @@ bool Monitor::Analyse() { } } Debug(3, "After motion detection, score:%d last_motion_score(%d), new motion score(%d)", - score, last_motion_score, motion_score); + score, last_motion_score, snap->score); motion_frame_count += 1; - last_motion_score = motion_score; + last_motion_score = snap->score; - if (motion_score) { + if (snap->score) { if (cause.length()) cause += ", "; cause += MOTION_CAUSE+std::string(":")+snap->alarm_cause; noteSetMap[MOTION_CAUSE] = zoneSet; @@ -2019,7 +2029,7 @@ bool Monitor::Analyse() { } else { Debug(1, "no image so skipping motion detection"); } // end if has image - score += last_motion_score; +//score += last_motion_score; } else { Debug(1, "Not Active(%d) enabled %d active %d doing motion detection: %d", Active(), enabled, shared_data->active, @@ -2032,17 +2042,17 @@ bool Monitor::Analyse() { if (event) { Debug(2, "Have event %" PRIu64 " in record", event->Id()); - if (section_length != Seconds(0) && (timestamp - event->StartTime() >= section_length) + if (section_length != Seconds(0) && (snap->timestamp - event->StartTime() >= section_length) && ((function == MOCORD && event_close_mode != CLOSE_TIME) || (function == RECORD && event_close_mode == CLOSE_TIME) - || std::chrono::duration_cast(timestamp.time_since_epoch()) % section_length == Seconds(0))) { + || std::chrono::duration_cast(snap->timestamp.time_since_epoch()) % section_length == Seconds(0))) { Info("%s: %03d - Closing event %" PRIu64 ", section end forced %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64 , name.c_str(), image_count, event->Id(), - static_cast(std::chrono::duration_cast(timestamp.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), static_cast(std::chrono::duration_cast(event->StartTime().time_since_epoch()).count()), - static_cast(std::chrono::duration_cast(timestamp - event->StartTime()).count()), + static_cast(std::chrono::duration_cast(snap->timestamp - event->StartTime()).count()), static_cast(Seconds(section_length).count())); closeEvent(); } // end if section_length @@ -2060,13 +2070,14 @@ bool Monitor::Analyse() { } // end if ! event } // end if RECORDING - if (score and (function != MONITOR)) { + if ((snap->score > 0) and (function != MONITOR)) { if ((state == IDLE) || (state == TAPE) || (state == PREALARM)) { // If we should end then previous continuous event and start a new non-continuous event if (event && event->Frames() && !event->AlarmFrames() && (event_close_mode == CLOSE_ALARM) - && ((timestamp - event->StartTime()) >= min_section_length) + // FIXME since we won't be including this snap in the event if we close it, we should be looking at event->duration() instead + && ((snap->timestamp - event->StartTime()) >= min_section_length) && ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count - 1))) { Info("%s: %03d - Closing event %" PRIu64 ", continuous end, alarm begins", name.c_str(), image_count, event->Id()); @@ -2078,7 +2089,7 @@ bool Monitor::Analyse() { Event::PreAlarmCount(), pre_event_count, event->Frames(), event->AlarmFrames(), - static_cast(std::chrono::duration_cast(timestamp - event->StartTime()).count()), + static_cast(std::chrono::duration_cast(snap->timestamp - event->StartTime()).count()), static_cast(Seconds(min_section_length).count()), (event_close_mode == CLOSE_ALARM)); } @@ -2088,12 +2099,10 @@ bool Monitor::Analyse() { if (!event) { event = openEvent(snap, cause, noteSetMap); - shared_data->state = state = ALARM; Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name.c_str(), analysis_image_count, event->Id()); - } else { - shared_data->state = state = ALARM; } // end if no event, so start it - if ( alarm_frame_count ) { + shared_data->state = state = ALARM; + if (alarm_frame_count) { Debug(1, "alarm frame count so SavePreAlarmFrames"); event->SavePreAlarmFrames(); } @@ -2115,13 +2124,13 @@ bool Monitor::Analyse() { Debug(1, "Was in TAPE, going into ALARM"); } else { Debug(1, "Staying in %s", State_Strings[state].c_str()); - } if (state == ALARM) { last_alarm_count = analysis_image_count; } // This is needed so post_event_count counts after last alarmed frames while in ALARM not single alarmed frames while ALERT - } else { // no score? + } else if (!score) { // no snap->score? alert_to_alarm_frame_count = alarm_frame_count; // load same value configured for alarm_frame_count + if (state == ALARM) { Info("%s: %03d - Gone into alert state", name.c_str(), analysis_image_count); shared_data->state = state = ALERT; @@ -2129,7 +2138,7 @@ bool Monitor::Analyse() { if ( ((analysis_image_count - last_alarm_count) > post_event_count) && - ((timestamp - event->StartTime()) >= min_section_length)) { + ((snap->timestamp - event->StartTime()) >= min_section_length)) { Info("%s: %03d - Left alarm state (%" PRIu64 ") - %d(%d) images", name.c_str(), analysis_image_count, event->Id(), event->Frames(), event->AlarmFrames()); if ( @@ -2156,7 +2165,7 @@ bool Monitor::Analyse() { analysis_image_count, last_alarm_count, post_event_count, - static_cast(std::chrono::duration_cast(timestamp.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), static_cast(std::chrono::duration_cast(GetVideoWriterStartTime().time_since_epoch()).count()), static_cast(Seconds(min_section_length).count())); } @@ -2164,7 +2173,8 @@ bool Monitor::Analyse() { Event::EmptyPreAlarmFrames(); } // end if score or not - snap->score = score; + if (score > snap->score) + snap->score = score; if (state == PREALARM) { // Generate analysis images if necessary @@ -2181,7 +2191,7 @@ bool Monitor::Analyse() { } // end if image. // incremement pre alarm image count - Event::AddPreAlarmFrame(snap->image, timestamp, score, nullptr); + Event::AddPreAlarmFrame(snap->image, snap->timestamp, score, nullptr); } else if (state == ALARM) { if (snap->image) { for (const Zone &zone : zones) { @@ -2198,12 +2208,12 @@ bool Monitor::Analyse() { if (event) { if (noteSetMap.size() > 0) event->updateNotes(noteSetMap); - if (section_length != Seconds(0) && (timestamp - event->StartTime() >= section_length)) { + if (section_length != Seconds(0) && (snap->timestamp - event->StartTime() >= section_length)) { Warning("%s: %03d - event %" PRIu64 ", has exceeded desired section length. %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64, name.c_str(), analysis_image_count, event->Id(), - static_cast(std::chrono::duration_cast(timestamp.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), static_cast(std::chrono::duration_cast(GetVideoWriterStartTime().time_since_epoch()).count()), - static_cast(std::chrono::duration_cast(timestamp - GetVideoWriterStartTime()).count()), + static_cast(std::chrono::duration_cast(snap->timestamp - GetVideoWriterStartTime()).count()), static_cast(Seconds(section_length).count())); closeEvent(); event = openEvent(snap, cause, noteSetMap);