Controlar cámara D-Link DSL-5222L con ZoneMinder

Si están buscando la mejor manera de deshacerse de una buena cantidad de dinero en el modo más inútil, les aconsejo vivamente comprar una cámara D-Link DSC-5222L. No tiene nada de nuevo. Una calidad bastante triste. Lenta. Para compensar toda esta la depresión y no sentirnos tan solos en el mundo de los estafados existe un remedio: ZoneMinder.

ZoneMinder es una solución open source que tal vez no sea la más completa de todas, pero es muy simple de instalar, configurar y utilizar. Hoy, no vamos a explicar nada de esto, que tan bien explicado está en los manuales de ZoneMinder.

Objetivo

Comfigurar y agregar una cámara D-Link DSC-5222L a ZoneMinder y poder comandarla desde la interfáz web.

Configurar la cámara

Con la cámara viene un antiguo CD con el software que configura todo con dos clicks. Claro, es necesario tener un lector y Windows (y un mouse para hacer los clicks). Si nos falta una de estas cosas, podemos simplemente conectarnos a la cámara a través de la dirección IP 192.168.0.20 e ingresar con el usuario admin sin contraseña.

Una vez dentro, configurar los siguientes puntos:

  • Setup → Network setup → LAN Settings
    • DHCP o IP estático
  • Setup → Wireless setup
    • Configurar la red wi-fi
  • Setup → Audio and Video → VIDEO PROFILE 1
    • Mode: JPEG
    • Frame size: 1280x720
    • Maximum frame rate: 25
    • Video quality: Excelent
  • Maintenance
    • Cambiar contraseña admin

Agregar la cámara a ZoneMinder

Entrar a la web de ZoneMinder e ingresar a add new monitor. Por ahora, configurar los siguientes parámetros:

General

  • Name: Casa
  • Server: None
  • Source Type: Remote

Source

  • Remote Protocol: HTTP
  • Remote Method: Simple
  • Remote Host Name: admin:password@ip_camara
  • Remote Host Port: 80 (o el que se haya configurado en la cámara)
  • Remote Host Path: /video/mjpg.cgi?profileid=1

Una vez configurados estos parámetros, ya deberíamos poder acceder al video de la D-Link. Si así no fuera, probar los parámetros remote host name y path en un browser para estar seguros de que el canal de video sea accesible.

Controlar la cámara con ZoneMinder

Lamentablemente, ZoneMinder no entenderá como controlar este modelo de cámara. Sí otras que son mejores, pero esta no. Para controlar la DSC-5222L será necesario primero cargar un script que le permitirá a ZoneMinder "conocer" los controles de la cámara.

En mi caso, ZoneMinder está instalado en un CentOS. La ubicación de los archivos puede cambiar de distribución a distribución. Para encontrar la que corresponde en nuestra instalación, podemos ejecutar:

find / -name "PanasonicIP.pm"

En el caso de CentOS, la ubicación es: /usr/share/perl5/vendor_perl/ZoneMinder/Control. Dentro este directorio creamos el archivo DCS_5222LB.pm

# =========================================================================r
#
# ZoneMinder D-Link DCS-5020L IP Control Protocol Module, $Date: $, $Revision: $
# Copyright (C) 2013 Art Scheel
#
# 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 D-Link DCS-5020L IP camera control
# protocol. 
#
package ZoneMinder::Control::DCS_5222LB;

use 5.006;
use strict;
use warnings;

require ZoneMinder::Base;
require ZoneMinder::Control;

our @ISA = qw(ZoneMinder::Control);

our $VERSION = $ZoneMinder::Base::VERSION;

# ==========================================================================
#
# D-Link DCS-5020L Control Protocol
#
# ==========================================================================

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 );
    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/" . ZoneMinder::Base::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 sendCmd2
{
    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 move
{
    my $self = shift;
    my $dir = shift;
    my $panSteps = shift;
    my $tiltSteps = shift;

    #my $cmd = "PanSingleMoveDegree=$panSteps&TiltSingleMoveDegree=$tiltSteps&PanTiltSingleMove=$dir";
    #action=move&direction=rightup&panstep=1&tiltstep=1
    my $cmd = "cgi-bin/longcctvmove.cgi?action=move&direction=$dir&panstep=$panSteps&tiltstep=$tiltSteps";
    $self->sendCmd( $cmd );
}

sub moveRelUpLeft
{
    my $self = shift;
    Debug( "Move Up Left" );
    $self->move( "leftup", 1, 1 );
}

sub moveRelUp
{
    my $self = shift;
    Debug( "Move Up" );
    $self->move( "up", 1, 1 );
}

sub moveRelUpRight
{
    my $self = shift;
    Debug( "Move Up" );
    $self->move( "rightup", 1, 1 );
}

sub moveRelLeft
{
    my $self = shift;
    Debug( "Move Left" );
    $self->move( "left", 1, 1 );
}

sub moveRelRight
{
    my $self = shift;
    Debug( "Move Right" );
    $self->move( "right", 1, 1 );
}

sub moveRelDownLeft
{
    my $self = shift;
    Debug( "Move Down" );
    $self->move( "leftdown", 1, 1 );
}

sub moveRelDown
{
    my $self = shift;
    Debug( "Move Down" );
    $self->move( "down", 1, 1 );
}

sub moveRelDownRight
{
    my $self = shift;
    Debug( "Move Down" );
    $self->move( "rightdown", 1, 1 );
}

# moves the camera to center on the point that the user clicked on in the video image. 
# This isn't extremely accurate but good enough for most purposes 
sub moveMap
{
    # if the camera moves too much or too little, try increasing or decreasing this value
    my $f = 11;

    my $self = shift;
    my $params = shift;
    my $xcoord = $self->getParam( $params, 'xcoord' );
    my $ycoord = $self->getParam( $params, 'ycoord' );

    my $hor = $xcoord * 100 / $self->{Monitor}->{Width};
    my $ver = $ycoord * 100 / $self->{Monitor}->{Height};
   
    my $direction;
    my $horSteps;
    my $verSteps;
    if ($hor < 50 && $ver < 50) {
        # up left
        $horSteps = (50 - $hor) / $f;
        $verSteps = (50 - $ver) / $f;
        $direction = "upleft";
    } elsif ($hor >= 50 && $ver < 50) {
        # up right
        $horSteps = ($hor - 50) / $f;
        $verSteps = (50 - $ver) / $f;
        $direction = "upright";
    } elsif ($hor < 50 && $ver >= 50) {
        # down left
        $horSteps = (50 - $hor) / $f;
        $verSteps = ($ver - 50) / $f;
        $direction = "downleft";
    } elsif ($hor >= 50 && $ver >= 50) {
        # down right
        $horSteps = ($hor - 50) / $f;
        $verSteps = ($ver - 50) / $f;
        $direction = "downright";
    }
    my $v = int($verSteps + .5);
    my $h = int($horSteps + .5);
    Debug( "Move Map to $xcoord,$ycoord, hor=$h, ver=$v with direction $direction" );
    $self->move( $direction, $h, $v );
}

# this clear function works, but should probably be disabled because 
# it isn't possible to set presets yet. 
sub presetClear
{
    my $self = shift;
    my $params = shift;
    my $preset = $self->getParam( $params, 'preset' );
    Debug( "Clear Preset $preset" );
    my $cmd = "ClearPosition=$preset";
    $self->sendCmd( $cmd );
}

# not working yet
sub presetSet
{
    my $self = shift;
    my $params = shift;
    my $preset = $self->getParam( $params, 'preset' );
    Debug( "Set Preset $preset" );
    # TODO need to first get current position $horPos and $verPos
    #my $cmd = "PanTiltHorizontal=$horPos&PanTiltVertical=$verPos&SetName=$preset&SetPosition=$preset";
    #$self->sendCmd( $cmd );
}

sub presetGoto
{
    my $self = shift;
    my $params = shift;
    my $preset = $self->getParam( $params, 'preset' );
    Debug( "Goto Preset $preset" );
    my $cmd = "PanTiltPresetPositionMove=$preset";
    $self->sendCmd( $cmd );
}

sub presetHome
{
    my $self = shift;
    Debug( "Home Preset" );
    my $cmd = "cgi-bin/longcctvhome.cgi?action=gohome";
    $self->sendCmd( $cmd );
}


#  IR Controls
#
#  wake = IR on
#  sleep = IR off
#  reset = IR auto


sub wake
{
    my $self = shift;
    Debug( "Wake - IR on" );
    my $cmd = "setDaynightMode?ReplySuccessPage=night.htm&ReplyErrorPage=errrnight.htm&DayNightMode=3&ConfigDayNightMode=Save";
    $self->sendCmd2( $cmd );
}

sub sleep
{
    my $self = shift;
    Debug( "Sleep - IR off" );
    my $cmd = "setDaynightMode?ReplySuccessPage=night.htm&ReplyErrorPage=errrnight.htm&DayNightMode=2&ConfigDayNightMode=Save";
    $self->sendCmd2( $cmd );
}

sub reset
{
    my $self = shift;
    Debug( "Reset - IR auto" );
    my $cmd = "setDaynightMode?ReplySuccessPage=night.htm&ReplyErrorPage=errrnight.htm&DayNightMode=0&ConfigDayNightMode=Save";
    $self->sendCmd2( $cmd );
}

1;
__END__
# Below is stub documentation for your module. You'd better edit it!

=head1 NAME

ZoneMinder::Database - Perl extension for DCS-5020L

=head1 SYNOPSIS

  use ZoneMinder::Database;
  DLINK DCS-5020L

=head1 DESCRIPTION

ZoneMinder driver for the D-Link consumer camera DCS-5020L.

=head2 EXPORT

None by default.



=head1 SEE ALSO

See if there are better instructions for the DCS-5020L at
http://www.zoneminder.com/wiki/index.php/Dlink

=head1 AUTHOR

Art Scheel <lt>ascheel (at) gmail<gt>

=head1 COPYRIGHT AND LICENSE

LGPLv3

=cut

Una vez cargado el script será necesario agregarlo a la lista de controladores de ZoneMinder. Entrar en la cámara cargada y seleccionar la pestaña control. Tildar la opción controllable y hacer click en edit al costado del select del control type.

ZoneMinder - Control capabilities

En la parte inferior de la ventana emergente, hacer click en add new control y configurar los siguientes parámetros:

Main

  • Name: Dlink DCS-5222LB
  • Type: Local
  • Protocol: DCS_5222LB
  • Can Wake: Sí
  • Can Sleep: Sí
  • Can Reset: Sí

Move

  • Can Move: Sí
  • Can Move Diagonally: Sí
  • Can Move Mapped: Sí
  • Can Move Absolute: No
  • Can Move Relative: Sí
  • Can Move Continuous: No

Pan (al igual que Tilt)

  • Can Pan: Sí
  • Min Pan Range: 0
  • Max Pan Range: 0
  • Min Pan Step: 1
  • Max Pan Step: 30
  • Has Pan Speed: No
  • Min Pan Speed: 0
  • Max Pan Speed: 0
  • Has Turbo Pan: No
  • Turbo Pan Speed: 0

Una vez configurado todo, seleccionar el nuevo controller type en la ventana de la cámara y configurar el campo control address con admin:password@ip_camara

Configuración del control de la cámara