Trendnet TV-IP651W(I) Control script

If you've made a patch to quick fix a bug or to add a new feature not yet in the main tree then post it here so others can try it out.
Post Reply
TheKorn
Posts: 142
Joined: Fri Aug 02, 2013 12:39 pm

Trendnet TV-IP651W(I) Control script

Post by TheKorn »

I'm a complete newb when it comes to zonemaster controls, so I imagine there is room to improve on this script. Nevertheless, here is version 1.0 which works. :D

If you're on ubuntu, drop this script in /usr/share/perl5/ZoneMinder/Control and call it TV-IP651.pm . If you're on another distro, find the file called "AxisV2.pm" and stick it in the same directory. :)

For those keeping track, I started with the TV-IP400 script and a network capture to figure out where it was going wrong when trying to control the TV-IP651. Turns out the user-agent was the key!

Code: Select all

# =========================================================================r
#
# ZoneMinder Trendnet TV-IP651W(I) IP Control Protocol Module, $Date: $, $Revision: $
# Copyright (C) 2013 Vincent G
#
# Based on TVIP400 by Peter Hulst & Philip Coombes (thanks for the huge head start, guys!)
#
#
#  For Zoneminder 1.25
#
#  Under control capability:
#
#   *  Main:  name it (suggest TVIP651), type is remote, protocol is TVIP651
#   *  Move:  Can move, can move diagonally, can move mapped, can move relative
#   *  Pan:  Can pan, min pan step 1, max pan step 10
#   *  Tilt:  Can tilt, min tilt step 1, max tilt step 10
#   *  Presets:  Has presets, num presets 24, has home preset
#
#  Under control tab in the monitor itself:
#
#   *  Controllable (duh)
#   *  Control type is the name you gave it in the control capability menu
#   *  Control device is the username you use to authenticate to the camera  (MUST MATCH the control address username!)
#   *  Control address is the camera's ip address, or if authenticating, user:pass@ip.address
#
#
# 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 Trendnet TV-IP651W(I) IP camera control
# protocol.

package ZoneMinder::Control::TVIP651;

use 5.006;
use strict;
use warnings;

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

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

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

# ==========================================================================
#
# Trendnet TV-IP651 Control Protocol
#
# ==========================================================================

#  changed per advice found on http://foscam.us/forum/zoneminder-1-25-0-with-foscam-fi8918w-control-problems-t3891.html .
#use ZoneMinder::Debug qw(:all);
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;

#   I'm not setting the user agent to be a jerk, the camera ACTUALLY CHECKS it! If it doesn't match the authentication username, everything fails!
    $self->{ua}->agent( "USER".$self->{Monitor}->{ControlDevice});

    $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( POST=>"http://".$self->{Monitor}->{ControlAddress}."/PANTILTCONTROL.CGI" );
    $req->content($cmd);
    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";
    $self->sendCmd( $cmd );
}

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

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

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

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

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

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

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

sub moveRelDownRight
{
    my $self = shift;
    Debug( "Move Down" );
    $self->move( 8, 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 = 0;
    } elsif ($hor >= 50 && $ver < 50) {
        # up right
        $horSteps = ($hor - 50) / $f;
        $verSteps = (50 - $ver) / $f;
        $direction = 2;
    } elsif ($hor < 50 && $ver >= 50) {
        # down left
        $horSteps = (50 - $hor) / $f;
        $verSteps = ($ver - 50) / $f;
        $direction = 6;
    } elsif ($hor >= 50 && $ver >= 50) {
        # down right
        $horSteps = ($hor - 50) / $f;
        $verSteps = ($ver - 50) / $f;
        $direction = 8;
    }
    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 = "PanTiltSingleMove=4";
    $self->sendCmd( $cmd );
}

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

=head1 NAME

ZoneMinder::Database - Perl extension for Trendnet TVIP651

=head1 SYNOPSIS

  use ZoneMinder::Database;
  stuff this in /usr/share/perl5/ZoneMinder/Control , then eat a sandwich

=head1 DESCRIPTION

Stub documentation for Trendnet TVIP651, created by Vince.

=head2 EXPORT

None by default.



=head1 SEE ALSO

Read the comments at the beginning of this file to see the usage for zoneminder 1.25.0


=head1 AUTHOR

Philip Coombes, E<lt>philip.coombes@zoneminder.comE<gt>
Vincent G, I'd rather you not email me.

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2005 by Philip Coombes

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.3 or,
at your option, any later version of Perl 5 you may have available.


=cut
nixnooi
Posts: 31
Joined: Mon Sep 04, 2006 1:02 am
Location: Pennsylvania, USA

Re: Trendnet TV-IP651W(I) Control script

Post by nixnooi »

Thank you! I was about to ask for help when I looked in the code. And there was all the help I needed. I've never made a new control type.

I appreciate you sharing.

And I just feel like posting this somewhere ... I run linux and none of my browsers would really work with the TV-IP651W. I don't have ActiveX** and the java was not actually functioning. If I click on the java I got redirected to http://192.168.1.538/JPlug.htm which was not working. Then I did a view source and saw an alternative - and that works! Hallelujah. I have had this camera for ages, and would just adjust it by hand. :lol: So the magic url for me was http://192.168.1.538/JWview.htm Typed directly into the browser.

and yes the ip is sanitized ...

** I don't do windows. I mean I REALLY don't do/have/tolerate windoze.
Post Reply