alarm cause from zmMemRead

Discussions related to the 1.36.x series of ZoneMinder
Post Reply
johndul0001
Posts: 21
Joined: Mon Jul 25, 2016 4:31 pm

alarm cause from zmMemRead

Post by johndul0001 »

Hello, I recently upgraded from 1.34 to 1.36

In 1.34 I was using a perl script with

my $alarmcause = zmMemRead($monitor, 'shared_data:alarm_cause');

$alarmcause used to include the name of the zone that went into alarm ex: "Motion Zone1"

now it just includes "Motion" without a zone name.

Is there any way for me to fix this?

Thanks in advance
User avatar
iconnor
Posts: 2881
Joined: Fri Oct 29, 2010 1:43 am
Location: Toronto
Contact:

Re: alarm cause from zmMemRead

Post by iconnor »

I'll have to take a look at the code. I think it should still do it.
johndul0001
Posts: 21
Joined: Mon Jul 25, 2016 4:31 pm

Re: alarm cause from zmMemRead

Post by johndul0001 »

Thanks, when you have a chance.
My code:

Code: Select all

#!/usr/bin/env perl

use strict;
use warnings;
use ZoneMinder;
use Switch;
use Data::Dumper;
use Time::HiRes qw(usleep);

my @monitors;
my $dbh = zmDbConnect();

my $sql = "SELECT * FROM Monitors WHERE Monitors.Id = '1'";

my $sth = $dbh->prepare_cached( $sql )
  or die( "Can't prepare '$sql': ".$dbh->errstr() );

my $res = $sth->execute()
  or die( "Can't execute '$sql': ".$sth->errstr() );

while ( my $monitor = $sth->fetchrow_hashref() ) {
    push( @monitors, $monitor );
}
while (1) {
    foreach my $monitor (@monitors) {
        # Check shared memory ok
        if ( !zmMemVerify( $monitor ) ) {
          zmMemInvalidate( $monitor );
          next;
         }
         my $alarmcause = zmMemRead($monitor, 'shared_data:alarm_cause');
		 
		 print Dumper($alarmcause);
	}
    usleep(1000000);
}
Result:

Code: Select all

$VAR1 = 'Motion';
$VAR1 = 'Motion';
$VAR1 = 'Motion';
$VAR1 = 'Motion';
$VAR1 = 'Motion';
$VAR1 = 'Motion';
$VAR1 = 'Motion';
$VAR1 = 'Motion';
$VAR1 = 'Motion';
$VAR1 = 'Motion';
$VAR1 = 'Motion';
$VAR1 = 'Motion';
$VAR1 = 'Motion';
$VAR1 = 'Motion';
$VAR1 = 'Motion';
$VAR1 = 'Motion';
$VAR1 = 'Motion';
$VAR1 = 'Motion';
$VAR1 = 'Motion';
johndul0001
Posts: 21
Joined: Mon Jul 25, 2016 4:31 pm

Re: alarm cause from zmMemRead

Post by johndul0001 »

Zoneminder v1.36.19 on Ubuntu 20.04.4 LTS (GNU/Linux 5.4.0-117-generic x86_64)
johndul0001
Posts: 21
Joined: Mon Jul 25, 2016 4:31 pm

Re: alarm cause from zmMemRead

Post by johndul0001 »

on my v1.34.26. system, I get this. It includes the zone name (in "Motion Ramp" or "Motion Shed")

Code: Select all

Cause: Motion Shed x: -1 y:-1
$VAR1 = 'Motion Shed';
$VAR1 = 'Motion Shed';
$VAR1 = 'Motion Shed';
$VAR1 = 'Motion Shed';
$VAR1 = 'Motion Shed';
$VAR1 = 'Motion Shed';
$VAR1 = 'Motion Ramp';
$VAR1 = 'Motion Ramp';
$VAR1 = 'Motion Ramp';
$VAR1 = 'Motion Ramp';
$VAR1 = 'Motion Ramp';
$VAR1 = 'Motion Ramp';
johndul0001
Posts: 21
Joined: Mon Jul 25, 2016 4:31 pm

Re: alarm cause from zmMemRead

Post by johndul0001 »

I am not competent at debugging perl but have done my best to figure out the alarm cause is no longer being written to the shared memory file. By stepping through the process, I believe the alarm cause should be at location 376 of my memory map for a length of 256 bytes.
line 152 of Mapped.pm:
my $data = substr($$mmap, $offset, $size);
However the zone name never appears at that location.
zmMemRead($monitor, 'trigger_data:trigger_cause');
doesn't work either

I think that information is probably written by "zma" but my abilities are far exceeded in trying to figure that out.

Direction would be appreciated.

I use the zone name from
zmMemRead($monitor, 'shared_data:trigger_cause');
to get the direction of movement. I treat the conditions differently if one zone triggers before the other.
_LD
Posts: 6
Joined: Mon Jun 20, 2022 11:02 pm

Re: alarm cause from zmMemRead

Post by _LD »

johndul0001 wrote: Fri Jun 10, 2022 8:20 pm

Code: Select all

         my $alarmcause = zmMemRead($monitor, 'shared_data:alarm_cause');
I dunno how the performance compares to the method you have been using, but perhaps you can try replacing the above snippet of code with the following line?

Code: Select all

my $alarmcause = zmGetMonitorState($monitor);
johndul0001
Posts: 21
Joined: Mon Jul 25, 2016 4:31 pm

Re: alarm cause from zmMemRead

Post by johndul0001 »

my $alarmcause = zmGetMonitorState($monitor);
Does not return the name of the zone in alarm which I need and is now missing in 1.36.

but thank you for your response.
_LD
Posts: 6
Joined: Mon Jun 20, 2022 11:02 pm

Re: alarm cause from zmMemRead

Post by _LD »

johndul0001 wrote: Mon Jun 20, 2022 11:21 pm
my $alarmcause = zmGetMonitorState($monitor);
Does not return the name of the zone in alarm which I need and is now missing in 1.36.

but thank you for your response.
Well of course it doesn't, because I'm stupid and pulled the wrong code.

This is the way I retrieve the zone(s) triggering the alarm state (by querying the most-recent event for the passed monitor):

Code: Select all

my $alarmcause = getZone($monitor_id);
#####
#####
#####
# return the zone that triggered the event alarm of the passed monitor
sub getZone {
	my ($monitor_id) = @_;

	my $sql = "SELECT Events.Notes FROM Events WHERE Events.Id IN (SELECT MAX(Events.Id) AS LastEventId FROM Monitors LEFT JOIN Events ON Monitors.Id = Events.MonitorId WHERE Monitors.Id = ".$monitor_id." GROUP BY (Monitors.id))";
	my $sth = $dbh->prepare_cached($sql) or die("Can't prepare '$sql': ".$dbh->errstr());
	my $res = $sth->execute() or die("Can't execute '$sql': ".$sth->errstr());

	while (my $monitorRow = $dbh->selectrow_hashref($sth)) {
		return $monitorRow->{Notes};
	}

	return "";
}
I'm sure there're cleaner methods of getting it from the Notes field, but that's the quick'n'dirty way I've been doing it.

EDIT: "$monitor_id" is just "$monitor->{Id}", BTW.
johndul0001
Posts: 21
Joined: Mon Jul 25, 2016 4:31 pm

Re: alarm cause from zmMemRead

Post by johndul0001 »

Again, you are awesome to respond

but:

The database query mode only works after the zone is no longer in alarm and the event is written (unless it has changed recently)

The method I have been using shows the zone name as soon as the alarm occurs. I actually use

zmGetMonitorState($monitor) and zmMemRead($monitor, 'shared_data:alarm_cause') together in an "and" condition to get the exact trigger I want.

the following worked fine until I upgraded from 1.34 to 1.36

Code: Select all

#!/usr/bin/env perl

use strict;
use warnings;
use ZoneMinder;
use Switch;
use Data::Dumper;

use Time::HiRes qw(usleep);

my @monitors;
my $dbh = zmDbConnect();
my $filename = '/var/log/zm-led-alarm.service.log';
my $message = localtime()." - Started";

open(my $fh, '>', $filename) or die "Could not open file '$filename' $!";
say $fh $message;
close $fh;

my $sql = "SELECT * FROM Monitors
  WHERE 
  Monitors.Id IN (1) AND 
  find_in_set( Function, 'Modect,Mocord,Nodect' )".
  ( $Config{ZM_SERVER_ID} ? 'AND ServerId=?' : '' )
  ;

my $sth = $dbh->prepare_cached( $sql )
  or die( "Can't prepare '$sql': ".$dbh->errstr() );

my $res = $sth->execute()
  or die( "Can't execute '$sql': ".$sth->errstr() );

while ( my $monitor = $sth->fetchrow_hashref() ) {
    push( @monitors, $monitor );
}
while (1) {
    foreach my $monitor (@monitors) {
        # Check shared memory ok
        if ( !zmMemVerify( $monitor ) ) {
          zmMemInvalidate( $monitor );
          next;
         }
         my $monitorState = zmGetMonitorState($monitor);
		 my $location = zmGetAlarmLocation($monitor);
         my $monitor_id = int($monitor->{Id});
         my $alarmcause = zmMemRead($monitor, 'shared_data:alarm_cause');
		 
		 # print Dumper($alarmcause);

         # writelog($filename,"Monitor ID $monitor_id, State $monitorState, Alarm Cause $alarmcause, Location $location");

         $sql = "SELECT COUNT(*) AS 'count' FROM Events WHERE MonitorId = '1' AND StartDateTime > NOW() - INTERVAL 5 minute";
         my $sth = $dbh->prepare( $sql )
           or die( "Can't prepare '$sql': ".$dbh->errstr() );
         my $res = $sth->execute()
           or die( "Can't execute '$sql': ".$sth->errstr() );
         my $count = $sth->fetchrow();

         # Zone 1 spray command
         if ($monitor->{Id} == 1 && $monitorState != 1 && index($alarmcause, "Motion Spray") == 0) {
            writelog($filename,"Zone 1 is in alarm from $alarmcause.");
            if ($count < 3) {
                if (!(-e '/home/www-data/opensprinkler_disable_bum_zone.lock')) {
                    system "/usr/bin/curl --silent \"http://192.168.69.231:8099/cm?pw=3e5b19f2ddfc206553356d12a26fedf7&sid=10&en=1&t=40\" > /dev/null 2>&1";
                    writelog($filename,"Spray Intruder");
                } else {
                    writelog($filename,"Bum Zone disabled by web interface. Spray aborted");
                }
            } else {
                writelog($filename,"3 events in 5 minutes. Spray aborted.");
            }
         }
         if ($monitor->{Id} == 1 && $monitorState == 2 && index($alarmcause, "Motion Spray") == -1) {
                writelog($filename,"Zone 1 is in alarm from $alarmcause. Waiting 30 seconds");
                sleep 30;
         }
    }
    usleep(500000);
}

sub writelog {
    my ($filename, $message) = @_;
    open(my $fh, '>>', $filename) or die "Could not open file '$filename' $!";
    say $fh localtime()." - ".$message;
    close $fh;
}
_LD
Posts: 6
Joined: Mon Jun 20, 2022 11:02 pm

Re: alarm cause from zmMemRead

Post by _LD »

I've only been using ZM since 1.36, so I dunno how it worked on 1.34, but the Notes field is definitely populated in real-time, while the event is still a "New Event". If you run the query several times while the event is ongoing, the field will continue to update with additional zones as they are triggered.

Sounds like a regression from 1.34 since the cleaner method you were using no longer works; perhaps worth a bug report? I might poke around later to see if I can tell what's changed between 1.34 and 1.36, as I don't care for the hacky method I've been using, honestly.

EDIT: Yeesh...there are a lot of changes in zm_monitor.cpp between 1.34, 1.36, and especially 1.37 (which looks much improved); 1.36 really feels like an amalgamation of 1.34 and 1.37. In 1.36, Monitor::openEvent supersedes simply creating a new Event, and alarm_cause in 1.37 is processed as a member of a ZMPacket, with the memory map file only actually being touched in Monitor::openEvent. It would be interesting to see if zmMemRead calls work as expected in a 1.37 installation.

It is evident that 1.36 is not operating as intended; having said that, none of the shared_data->alarm_cause updates stand out as the offender (I've only looked at the raw text, not properly debugged a build).

Please do update this thread if you locate the problematic code, as I would like to correct it on my own install.

P.S. You are absolutely correct: the zones are not being written to the memory map file, only updated in the database.
johndul0001
Posts: 21
Joined: Mon Jul 25, 2016 4:31 pm

Re: alarm cause from zmMemRead

Post by johndul0001 »

Thanks to your pointers I got
#!/usr/bin/env perl

use strict;
use warnings;
use ZoneMinder;
use Switch;
use Data::Dumper;
use Time::HiRes qw(usleep);

my @monitors;
my $res;
my $sth;
my $row;
my $note;

my $dbh = zmDbConnect();

my $sql = "SELECT * FROM Monitors WHERE Monitors.Id = '12'";

$sth = sqlquery($sql);
my $monitor = $sth->fetchrow_hashref();

checkmem($monitor);

my $lastid = zmGetLastEvent($monitor);

while (1) {
checkmem($monitor);

if ($lastid != zmGetLastEvent($monitor)) {

$lastid = zmGetLastEvent($monitor);

$sql = "SELECT Notes FROM Events WHERE Id = $lastid";

$sth = sqlquery($sql);

$row = $sth->fetchrow_hashref();
$note = $row->{Notes};

print "ID: $lastid Note: $note\n";
}
usleep(1000000);
}

sub checkmem {
# Check shared memory ok
if ( !zmMemVerify( $_[0] ) ) {
zmMemInvalidate( $_[0] );
next;
}
}

sub sqlquery {
$sth = $dbh->prepare_cached( $_[0] )
or die( "Can't prepare '$_[0]': ".$dbh->errstr() );

$res = $sth->execute()
or die( "Can't execute '$_[0]': ".$sth->errstr() );

$_[0] = $sth;
}
to work.

I hope the mapped method gets fixed. It is beyond my skills at the moment.

along the same lines, Have you ever been able to get

Code: Select all

zmMemRead($monitor, 'shared_data:alarm_x') or 
zmMemRead($monitor, 'shared_data:alarm_y')
to work? They always return "-1" for me.
These would also be helpful to me as I could use them to determine direction.
johndul0001
Posts: 21
Joined: Mon Jul 25, 2016 4:31 pm

Re: alarm cause from zmMemRead

Post by johndul0001 »

Here is a video compilation of what happens when it works. People can use the exit door and leave from the left without a problem. But when people approach the camera from the right, it activates an irrigation valve.
https://www.youtube.com/watch?v=QvIKKS0OE2A
User avatar
iconnor
Posts: 2881
Joined: Fri Oct 29, 2010 1:43 am
Location: Toronto
Contact:

Re: alarm cause from zmMemRead

Post by iconnor »

Just an FYI, I made sorta nice ORM objects, so you can do :

require ZoneMinder::Monitor;

foreach my (monitor (ZoneMinder::Monitor->find(Function=>'Modect')) {
etc....
}

Also..

require ZoneMinder::Event;
my $event = new ZoneMinder::Event($event_id);
print $event->Notes();

etc...
Post Reply