Foscam F9821P V2 Control Script for Zoneminder
A Zoneminder control script for the Foscam F9821P V2 Camera that is modifed from F9821W. Tested fulling working with Zoneminder version 1.34
Step 1: Save the following script as FoscamFI9821PV2.pm to your Zoneminder camera control folder. Reboot Zoneminder.
Step 2: Add a new control method and enter "FoscamFI9821PV2" in the protocol field and tick the settings as below:
Main tab:
Name - Foscam FI9821P V2
Type - Remote
Protocol - FoscamFI9821PV2
Can Wake - ticked (For IR control)
Can Sleep - ticked (For IR control)
Can Reset - ticked (For reboot function)
Move tab:
Can Move - ticked
Can Move Diagonally - ticked
Can Move Relative - ticked
Can Move Continuous - ticked
Pan tab:
Can Pan - ticked
Tilt tab:
Can Tilt - ticked
Presets tab:
Has Presets - ticked
Num Presets - 16
Has Home Preset - ticked
Can Set Presets - ticked
Monitor - Control tab
Controllable - ticked
Control type - Foscam FI9821P V2
Control device - usr=replace_with_username&pwd=replace_with_password
Control address - http://Camera IP address:88
Auto Stop Timeout - 1 (or longer if you want camera to move further with each button press)
- Enter username and password in the "Control Device" field
E.g. usr=admin&pwd=password
- To set presets, you must enter a label name
- To use existing presets in camera, just pick any preset number in zoneminder and set a label name matching the one in camera. The number and order of preset does not matter.
# ==========================================================================
#
# ZoneMinder Foscam FI9821PV2 IP Control Protocol Module
#
# Created from Dave Harris Foscam FI8918W control script - Feb 2011
# Modified by Henry Chang on 17/06/2020 to work with Foscam FI9821PV2
#
# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# ==========================================================================
package ZoneMinder::Control::FoscamFI9821PV2;
use 5.006;
use strict;
use warnings;
require ZoneMinder::Base;
require ZoneMinder::Control;
our @ISA = qw(ZoneMinder::Control);
# ==========================================================================
#
# Foscam FI9821P V2 IP Control Protocol
#
# ==========================================================================
use ZoneMinder::Logger qw(:all);
use ZoneMinder::Config qw(:all);
use ZoneMinder::Database qw(zmDbConnect);
use XML::LibXML;
use Time::HiRes qw( usleep );
sub new {
my $class = shift;
my $id = shift;
my $self = ZoneMinder::Control->new( $id );
my $logindetails = "";
bless( $self, $class );
srand( time() );
return $self;
}
our $AUTOLOAD;
sub AUTOLOAD {
my $self = shift;
my $class = ref($self) || croak( "$self not object" );
my $name = $AUTOLOAD;
$name =~ s/.*://;
if ( exists($self->{$name}) )
{
return( $self->{$name} );
}
Fatal( "Can't access $name member of object of class $class" );
}
sub open {
my $self = shift;
$self->loadMonitor();
use LWP::UserAgent;
$self->{ua} = LWP::UserAgent->new;
$self->{ua}->agent( "ZoneMinder Control Agent/" );
$self->{state} = 'open';
$self->loadPresets();
}
#Load presets from camera into Zoneminder
sub loadPresets {
my $self = shift;
my $dom = XML::LibXML->load_xml(location => "http://".$self->{Monitor}->{ControlAddress}."/cgi-bin/CGIProxy.fcgi?cmd=getPTZPresetPointList&".$self->{Monitor}->{ControlDevice});
my $counter = 1;
my $dbh = zmDbConnect(1);
my $sql_del = 'DELETE FROM ControlPresets where MonitorId= ?';
my $sth_del = $dbh->prepare($sql_del);
my $res_del = $sth_del->execute($self->{Monitor}->{Id});
my $sql = 'INSERT INTO ControlPresets (Label,MonitorId,Preset) VALUES (?,?,?)';
my $sth = $dbh->prepare($sql);
foreach my $cam_label ($dom->findnodes("//*[starts-with(local-name(),'point')]") ){
my $res = $sth->execute($cam_label->to_literal(),$self->{Monitor}->{Id},$counter);
$counter++;
}
}
sub close {
my $self = shift;
$self->{state} = 'closed';
}
sub printMsg {
my $self = shift;
my $msg = shift;
my $msg_len = length($msg);
Debug( $msg."[".$msg_len."]" );
}
sub sendCmd {
my $self = shift;
my $cmd = shift;
my $result = undef;
printMsg( $cmd, "Tx" );
my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/$cmd".$self->{Monitor}->{ControlDevice} );
my $res = $self->{ua}->request($req);
if ( $res->is_success )
{
$result = !undef;
}
else
{
Error( "Error check failed:'".$res->status_line()."'" );
}
return( $result );
}
sub autoStop
{
my $self = shift;
my $autostop = shift;
if( $autostop )
{
Debug( "Auto Stop" );
usleep( $autostop );
my $cmd = "cgi-bin/CGIProxy.fcgi?cmd=ptzStopRun&";
$self->sendCmd( $cmd );
}
}
#Reboot camera
sub reset {
my $self = shift;
Debug( "Camera Reset" );
my $cmd = "cgi-bin/CGIProxy.fcgi?cmd=rebootSystem&";
$self->sendCmd( $cmd );
}
#Up Arrow
sub moveConUp {
my $self = shift;
Debug( "Move Up" );
my $cmd = "cgi-bin/CGIProxy.fcgi?cmd=ptzMoveUp&";
$self->sendCmd( $cmd );
$self->autoStop( $self->{Monitor}->{AutoStopTimeout} );
}
#Down Arrow
sub moveConDown {
my $self = shift;
Debug( "Move Down" );
my $cmd = "cgi-bin/CGIProxy.fcgi?cmd=ptzMoveDown&";
$self->sendCmd( $cmd );
$self->autoStop( $self->{Monitor}->{AutoStopTimeout} );
}
#Left Arrow
sub moveConLeft {
my $self = shift;
Debug( "Move Left" );
my $cmd = "cgi-bin/CGIProxy.fcgi?cmd=ptzMoveLeft&";
$self->sendCmd( $cmd );
$self->autoStop( $self->{Monitor}->{AutoStopTimeout} );
}
#Right Arrow
sub moveConRight {
my $self = shift;
Debug( "Move Right" );
my $cmd = "cgi-bin/CGIProxy.fcgi?cmd=ptzMoveRight&";
$self->sendCmd( $cmd );
$self->autoStop( $self->{Monitor}->{AutoStopTimeout} );
}
#Diagonally Up Right Arrow
sub moveConUpRight {
my $self = shift;
Debug( "Move Diagonally Up Right" );
my $cmd = "cgi-bin/CGIProxy.fcgi?cmd=ptzMoveTopRight&";
$self->sendCmd( $cmd );
$self->autoStop( $self->{Monitor}->{AutoStopTimeout} );
}
#Diagonally Down Right Arrow
sub moveConDownRight {
my $self = shift;
Debug( "Move Diagonally Down Right" );
my $cmd = "cgi-bin/CGIProxy.fcgi?cmd=ptzMoveBottomRight&";
$self->sendCmd( $cmd );
$self->autoStop( $self->{Monitor}->{AutoStopTimeout} );
}
#Diagonally Up Left Arrow
sub moveConUpLeft {
my $self = shift;
Debug( "Move Diagonally Up Left" );
my $cmd = "cgi-bin/CGIProxy.fcgi?cmd=ptzMoveTopLeft&";
$self->sendCmd( $cmd );
$self->autoStop( $self->{Monitor}->{AutoStopTimeout} );
}
#Diagonally Down Left Arrow
sub moveConDownLeft {
my $self = shift;
Debug( "Move Diagonally Down Left" );
my $cmd = "cgi-bin/CGIProxy.fcgi?cmd=ptzMoveBottomLeft&";
$self->sendCmd( $cmd );
$self->autoStop( $self->{Monitor}->{AutoStopTimeout} );
}
#Stop
sub moveStop {
my $self = shift;
Debug( "Move Stop" );
my $cmd = "cgi-bin/CGIProxy.fcgi?cmd=ptzStopRun&";
$self->sendCmd( $cmd );
}
#Move Camera to Home Position
sub presetHome {
my $self = shift;
Debug( "Home Preset" );
my $cmd = "cgi-bin/CGIProxy.fcgi?cmd=ptzReset&";
$self->sendCmd( $cmd );
}
#Set preset
sub presetSet {
my $self = shift;
my $params = shift;
my $preset = $self->getParam($params, 'preset');
#load preset name from camera
my $old_preset = $preset-1;
my $dom = XML::LibXML->load_xml(location => "http://".$self->{Monitor}->{ControlAddress}."/cgi-bin/CGIProxy.fcgi?cmd=getPTZPresetPointList&".$self->{Monitor}->{ControlDevice});
my $old_label = $dom ->findvalue("/CGI_Result/point$old_preset");
my $delete_old_label_cmd = "cgi-bin/CGIProxy.fcgi?cmd=ptzDeletePresetPoint&name=$old_label&";
$self->sendCmd( $delete_old_label_cmd);
my $dbh = zmDbConnect(1);
my $sql = 'SELECT * FROM ControlPresets WHERE MonitorId = ? AND Preset = ?';
my $sth = $dbh->prepare($sql);
my $res = $sth->execute($self->{Monitor}->{Id}, $preset);
my $ref = ($sth->fetchrow_hashref());
my $label = $ref->{'Label'};
my $cmd = "cgi-bin/CGIProxy.fcgi?cmd=ptzAddPresetPoint&name=$label&";
$self->sendCmd( $cmd );
$self->loadPresets();
}
#Goto preset
sub presetGoto {
my $self = shift;
my $params = shift;
my $preset = $self->getParam($params, 'preset');
my $dbh = zmDbConnect(1);
my $sql = 'SELECT * FROM ControlPresets WHERE MonitorId = ? AND Preset = ?';
my $sth = $dbh->prepare($sql);
my $res = $sth->execute($self->{Monitor}->{Id}, $preset);
my $ref = ($sth->fetchrow_hashref());
my $label = $ref->{'Label'};
Debug( "Goto Preset $preset" );
my $cmd = "cgi-bin/CGIProxy.fcgi?cmd=ptzGotoPresetPoint&name=$label&";
$self->sendCmd( $cmd );
}
#Turn IR on
sub wake {
my $self = shift;
Debug( "Wake - IR on" );
my $cmd = "cgi-bin/CGIProxy.fcgi?cmd=openInfraLed&";
$self->sendCmd( $cmd );
}
#Turn IR off
sub sleep {
my $self = shift;
Debug( "Sleep - IR off" );
my $cmd = "cgi-bin/CGIProxy.fcgi?cmd=closeInfraLed&";
$self->sendCmd( $cmd );
}
1;
If you like my work, feel free to buy me a coffee: