Loftek
NOTE: This is my first attempt at creating a Wiki page. Please excuse the formatting (or lack thereof) as I go through my learning curve.
TODO: Add Photo of camera
Loftek Sentinel
This is an outdoor PTZ IP Camera with 3x Optical Zoom. The commands to operate this camera are very similar to a Foscam camera [2] except with a different set of features. It has been observed that companies such as Hootoo and Apexis offer cameras that appear identical to this Loftek model. The following instructions will likely work for those models as well.
Create the Monitor
From the Zoneminder console, click "Add New Monitor" and enter the following:
- General
- Name: {give the camera a name}
- Source Type: Remote
- Function: {select an appropriate function from the dropdown}
- Enabled: Tick
- Maximum FPS: Empty
- Alarm Maximum FPS: Empty
- Source
- Remote Protocol: HTTP
- Remote Method: Simple
- Remote Host Name: x.x.x.x {The IP of the camera}
- Remote Host Port: 80
- Remote Host Path: /videostream.cgi?user=admin&pwd={password}&resolution=32&rate=11
- Remote Image Colours: 24 bit colour
- Capture Width: 640
- Capture Height: 480
This will give you a 640x480 video stream running at ~5 fps. Refer to the table on the Foscam page [1] for other frame rate settings. Unlike the Foscam's , I have not run into any issues running at 640x80.
PTZ Control
I used a number of sources to create the following control file [2][3][4].
Here are the supported functions:
- Camera reset
- Pan left/right
- Tilt up/down
- Emulated diagonal movement
- Zoom in/out
- Brightness (Iris)
- Contrast (White)
- Can set & recall up to 8 presets
- Presets 9 & 10 respectively start and stop a patrol
Zoneminder must be taught how to control the Loftek camera. This is done with a PTZ control file. First, copy and paste the following code into a text file called "LoftekSentinel.pm". Save the file into the same folder as the file called "PanasonicIP.pm".
# ========================================================================== # # ZoneMinder Loftek Sentinel IP Control Protocol Module # Copyright (C) 2001-2008 Philip Coombes # Original modification for use with Foscam FI8908W IP Camera by Dave Harris # Updated by Ivan Francolin Martinez # Converted for use with Loftek Sentinal IP Camera by Andrew Bauer (knnniggett@users.sourceforge.net) # # 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # # ========================================================================== # # This module contains the implementation of the Loftek Sentinel IP camera control # protocol # package ZoneMinder::Control::LoftekSentinel; use 5.006; use strict; use warnings; require ZoneMinder::Base; require ZoneMinder::Control; our @ISA = qw(ZoneMinder::Control); our $VERSION = $ZoneMinder::Base::VERSION; our %CamParams = (); # ========================================================================== # # Loftek Sentinel IP Control Protocol # # On ControlAddress use the format : # USERNAME:PASSWORD@ADDRESS:PORT # eg : admin:@10.1.2.1:80 # zoneminder:zonepass@10.0.100.1:40000 # # ========================================================================== # Change "ZoneMinder::Logger" to "ZoneMinder::Debug" for zoneminder < 1.25.0 use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); 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( ) || 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/".ZM_VERSION ); $self->{state} = 'open'; } 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" ); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { Error( "Error check failed:'".$res->status_line()."'" ); } return( $result ); } sub getCamParams { my $self = shift; my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/get_camera_params.cgi" ); my $res = $self->{ua}->request($req); if ( $res->is_success ) { # Parse results setting values in %FCParams my $content = $res->decoded_content; while ($content =~ s/var\s+([^=]+)=([^;]+);//ms) { $CamParams{$1} = $2; } } else { Error( "Error check failed:'".$res->status_line()."'" ); } } #autoStop #This makes use of the ZoneMinder Auto Stop Timeout on the Control Tab sub autoStop { my $self = shift; my $stop_command = shift; my $autostop = shift; if( $stop_command && $autostop) { Debug( "Auto Stop" ); usleep( $autostop ); my $cmd = "decoder_control.cgi?command=".$stop_command; $self->sendCmd( $cmd ); } } # Reset the Camera sub reset { my $self = shift; Debug( "Camera Reset" ); my $cmd = "reboot.cgi?"; $self->sendCmd( $cmd ); } #Up Arrow sub moveConUp { my $self = shift; my $stop_command = "1"; Debug( "Move Up" ); my $cmd = "decoder_control.cgi?command=0"; $self->sendCmd( $cmd ); $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); } #Down Arrow sub moveConDown { my $self = shift; my $stop_command = "3"; Debug( "Move Down" ); my $cmd = "decoder_control.cgi?command=2"; $self->sendCmd( $cmd ); $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); } #Left Arrow sub moveConLeft { my $self = shift; my $stop_command = "5"; Debug( "Move Left" ); my $cmd = "decoder_control.cgi?command=4"; $self->sendCmd( $cmd ); $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); } #Right Arrow sub moveConRight { my $self = shift; my $stop_command = "7"; Debug( "Move Right" ); my $cmd = "decoder_control.cgi?command=6"; $self->sendCmd( $cmd ); $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); } #Zoom In sub zoomConTele { my $self = shift; my $stop_command = "17"; Debug( "Zoom Tele" ); my $cmd = "decoder_control.cgi?command=18"; $self->sendCmd( $cmd ); $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); } #Zoom Out sub zoomConWide { my $self = shift; my $stop_command = "19"; Debug( "Zoom Wide" ); my $cmd = "decoder_control.cgi?command=16"; $self->sendCmd( $cmd ); $self->autoStop( $stop_command, $self->{Monitor}->{AutoStopTimeout} ); } #Diagonally Up Right Arrow #This camera does not have builtin diagonal commands so we emulate them sub moveConUpRight { my $self = shift; Debug( "Move Diagonally Up Right" ); $self->moveConUp( ); $self->moveConRight( ); } #Diagonally Down Right Arrow #This camera does not have builtin diagonal commands so we emulate them sub moveConDownRight { my $self = shift; Debug( "Move Diagonally Down Right" ); $self->moveConDown( ); $self->moveConRight( ); } #Diagonally Up Left Arrow #This camera does not have builtin diagonal commands so we emulate them sub moveConUpLeft { my $self = shift; Debug( "Move Diagonally Up Left" ); $self->moveConUp( ); $self->moveConLeft( ); } #Diagonally Down Left Arrow #This camera does not have builtin diagonal commands so we emulate them sub moveConDownLeft { my $self = shift; Debug( "Move Diagonally Down Left" ); $self->moveConDown( ); $self->moveConLeft( ); } #Stop sub moveStop { my $self = shift; Debug( "Move Stop" ); my $cmd = "decoder_control.cgi?command=1"; $self->sendCmd( $cmd ); } #Set Camera Preset #Presets must be translated into values internal to the camera #Those values are: 30,32,34,36,38,40,42,44 for presets 1-8 respectively sub presetSet { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Debug( "Set Preset $preset" ); if (( $preset >= 1 ) && ( $preset <= 8 )) { my $cmd = "decoder_control.cgi?command=".(($preset*2) + 28); $self->sendCmd( $cmd ); } } #Recall Camera Preset #Presets must be translated into values internal to the camera #Those values are: 31,33,35,37,39,41,43,45 for presets 1-8 respectively sub presetGoto { my $self = shift; my $params = shift; my $preset = $self->getParam( $params, 'preset' ); Debug( "Goto Preset $preset" ); if (( $preset >= 1 ) && ( $preset <= 8 )) { my $cmd = "decoder_control.cgi?command=".(($preset*2) + 29); $self->sendCmd( $cmd ); } if ( $preset == 9 ) { $self->horizontalPatrol(); } if ( $preset == 10 ) { $self->horizontalPatrolStop(); } } #Horizontal Patrol - Vertical Patrols are not supported sub horizontalPatrol { my $self = shift; Debug( "Horizontal Patrol" ); my $cmd = "decoder_control.cgi?command=20"; $self->sendCmd( $cmd ); } #Horizontal Patrol Stop sub horizontalPatrolStop { my $self = shift; Debug( "Horizontal Patrol Stop" ); my $cmd = "decoder_control.cgi?command=21"; $self->sendCmd( $cmd ); } # Increase Brightness sub irisAbsOpen { my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{'brightness'}); my $step = $self->getParam( $params, 'step' ); $CamParams{'brightness'} += $step; $CamParams{'brightness'} = 255 if ($CamParams{'brightness'} > 255); Debug( "Iris $CamParams{'brightness'}" ); my $cmd = "camera_control.cgi?param=1&value=".$CamParams{'brightness'}; $self->sendCmd( $cmd ); } # Decrease Brightness sub irisAbsClose { my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{'brightness'}); my $step = $self->getParam( $params, 'step' ); $CamParams{'brightness'} -= $step; $CamParams{'brightness'} = 0 if ($CamParams{'brightness'} < 0); Debug( "Iris $CamParams{'brightness'}" ); my $cmd = "camera_control.cgi?param=1&value=".$CamParams{'brightness'}; $self->sendCmd( $cmd ); } # Increase Contrast sub whiteAbsIn { my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{'contrast'}); my $step = $self->getParam( $params, 'step' ); $CamParams{'contrast'} += $step; $CamParams{'contrast'} = 6 if ($CamParams{'contrast'} > 6); Debug( "Iris $CamParams{'contrast'}" ); my $cmd = "camera_control.cgi?param=2&value=".$CamParams{'contrast'}; $self->sendCmd( $cmd ); } # Decrease Contrast sub whiteAbsOut { my $self = shift; my $params = shift; $self->getCamParams() unless($CamParams{'contrast'}); my $step = $self->getParam( $params, 'step' ); $CamParams{'contrast'} -= $step; $CamParams{'contrast'} = 0 if ($CamParams{'contrast'} < 0); Debug( "Iris $CamParams{'contrast'}" ); my $cmd = "camera_control.cgi?param=2&value=".$CamParams{'contrast'}; $self->sendCmd( $cmd ); } 1;
Once the PTZ control file has been saved, open the Monitor created previously and click on the "Control" tab. Next click on "Edit" and then the "Add New Control" button. Enter the following information at each tab:
- Main
- Name: Loftek Sentinel
- Type: Remote
- Protocol: LoftekSentinel (notice no space)
- Can reset: Tick
- Move
- Can Move: Tick
- Can Move Diagonally: Tick
- Can Move Continuous: Tick
- Pan
- Can Pan: Tick
- Tilt
- Can Tilt: Tick
- Zoom
- Can Zoom: Tick
- Can Zoom Continuous: Tick
- White
- Can White Balance: Tick
- Can White Balance Absolute: Tick
- Max White Bal. Range: 6
- Min White Bal. Step: 1
- Max White Bal. Step: 1
- Iris
- Can Iris: Tick
- Can Iris Absolute: Tick
- Max Iris Range: 255
- Min Iris Step: 16
- Max Iris Step: 16
- Presets
- Has Presets: Tick
- Num Presets: 10
- Can Set Presets: Tick
When finished, click the "Save" button.
INCOMPLETE: Finish instructions
- Control
- Controllable: Tick
- Control Type: Loftek Sentinel
- Control Device: empty
- Control Address: admin:{password}@{camera ip address}
- Auto Stop Timeout: 0.50 (or whatever works best for you)
Notes & Troubleshooting
- The PTZ Control File is compatible with Zoneminder 1.25.0. If using an older version, you may have to change the line that reads "use ZoneMinder::Logger qw(:all);" to "use ZoneMinder::Debug qw(:all);" .
- TODO: Add more notes
References
[1] Foscam Forum Thread
[2] Foscam Wiki Page
[3] Dave Harris Blog Site
[4] IP Cam CGI SDK