New support for PWC PTZ Logitech Quickcam Orbit / Sphere etc

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
zylantha
Posts: 8
Joined: Mon Feb 27, 2006 10:36 pm

New support for PWC PTZ Logitech Quickcam Orbit / Sphere etc

Post by zylantha »

I have managed to find time to knock up a quick script that will control the pan/tilt of a Quickcam Orbit using the PWC devfs interface. It is based on the ncs370 script (for whatever reason).

Control Capabilities:
Name = Logitech Orbit
Type = Local
Command = zmcontrol-pwc.pl

Move Options:
Can Move
Can Move Diagonally
Can Move Mapped
Can Move Absolute
Can Move Relative

Pan Options:
Can Pan
Min Pan Range 0
Max Pan Range 6500
Min Pan Step 100
Max Pan Step 500
Has Pan Speed
Min Pan Speed 1
Max Pan Speed 1

Tilt Options:
Can Tilt
Min Tilt Range 0
Max Tilt Range 2500
Min Tilt Step 100
Max Tilt Step 500
Has Tilt Speed
Min Tilt Speed 1
Max Tilt Speed 1

ALL OTHER CONTROL CAPABILITIES OFF

Monitor Control options:
Controllable
Control Type Logitech Orbit
Control Device /sys/class/video4linux/video0/pan_tilt

NB: The above Control Device is the key to being able to control the Orbit without direct access to the device (which is taken up by ZoneMinder).

Remember to make sure that the control device is writable by the ZoneMinder user (apache), e.g. chmod 777 /sys/class/video4linux/video0/pan_tilt

zmcontrol-pwc.pl:

Code: Select all

#!/usr/bin/perl -wT
#
# ==========================================================================
#
# ZoneMinder PWC Control Script, $Date: 2007/07/15 12:37:48 $, $Revision: 1.0 $
# Copyright (C) 2007 Denis Cheong
# Portions Copyright (C) 2003, 2004, 2005, 2006  Philip Coombes
#
# 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 script continuously monitors the recorded events for the given
# monitor and applies any filters which would delete and/or upload
# matching events
#
use strict;

# ==========================================================================
#
# These are the elements you can edit to suit your installation
#
# ==========================================================================

use constant DBG_ID => "zmctrl-pwc"; # Tag that appears in debug to identify source
use constant DBG_LEVEL => 0; # 0 is errors, warnings and info only, > 0 for debug

use ZoneMinder;
use Getopt::Long;

$| = 1;

$ENV{PATH}  = '/bin:/usr/bin';
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};

sub Usage
{
        print( "
Usage: zmcontrol-pwc.pl <various options>
");
        exit( -1 );
}

zmDbgInit( DBG_ID, level=>DBG_LEVEL );

my $arg_string = join( " ", @ARGV );

my $pan_min = 0;
my $pan_max = 6500;
my $tilt_min = 0;
my $tilt_max = 2500;

my $address;
my $device;
my $command;
my ( $speed, $step );
my ( $xcoord, $ycoord );
my ( $width, $height );
my ( $panspeed, $tiltspeed );
my ( $panstep, $tiltstep );
my $preset;

Debug( $arg_string."\n" );

if ( !GetOptions(
        'device=s'=>\$device,
        'command=s'=>\$command,
        'speed=i'=>\$speed,
        'step=i'=>\$step,
        'xcoord=i'=>\$xcoord,
        'ycoord=i'=>\$ycoord,
        'width=i'=>\$width,
        'height=i'=>\$height,
        'panstep=i'=>\$panstep,
        'panspeed=i'=>\$panspeed,
        'tiltstep=i'=>\$tiltstep,
        'tiltspeed=i'=>\$tiltspeed
        )
)
{
        Debug ("Incorrect parameters");

        Usage();
}

if ( !$device )
{
        Usage();
}

# We have a device ... where is it now?

open(DAT, $device) || Debug ("Could not open $device");
my ($pan_cur,$tilt_cur)=split(/\ /,<DAT>);
$tilt_cur = $tilt_cur + 0;
close(DAT);

Debug ( "Currently at $pan_cur,$tilt_cur" );




srand( time() );

sub printMsg
{
        my $msg = shift;
        my $msg_len = length($msg);

        Debug( $msg."[".$msg_len."]\n" );
}

sub moveUp
{
        Debug( "Move Up\n" );
        setPanTilt($pan_cur, $tilt_cur + $tiltstep);
}

sub moveDown
{
        Debug( "Move Down\n" );
        setPanTilt($pan_cur, $tilt_cur - $tiltstep);
}

sub moveLeft
{
        Debug( "Move Left\n" );
        setPanTilt($pan_cur - $panstep, $tilt_cur);
}

sub moveRight
{
        Debug( "Move Right\n" );
        setPanTilt($pan_cur + $panstep, $tilt_cur);
}

sub moveUpRight
{
        setPanTilt($pan_cur + $panstep, $tilt_cur + $tiltstep);
}

sub moveUpLeft
{
        setPanTilt($pan_cur - $panstep, $tilt_cur + $tiltstep);
}

sub moveDownRight
{
        setPanTilt($pan_cur + $panstep, $tilt_cur - $tiltstep);
}

sub moveDownLeft
{
        setPanTilt($pan_cur - $panstep, $tilt_cur - $tiltstep);
}

sub setPanTilt
{
        my ( $pan, $tilt ) = @_;

        if ($pan > $pan_max) {
                $pan = $pan_max;
        };
        if ($pan < 0) {
                $pan = 0;
        };
        if ($tilt >  $tilt_max) {
                $tilt = $tilt_max;
        };
        if ($tilt < 0) {
                $tilt = 0;
        };

        Debug( "Moving from $pan_cur,$tilt_cur to $pan,$tilt\n" );

        open(DAT,">$device") || die("Cannot open $device");
        print(DAT "$pan $tilt");
        close(DAT);
}

sub stepUp
{
        my $step = shift;
        Debug( "Step Up $step\n" );
        setPanTilt($pan_cur, $tilt_cur+$step);
}

if ( $command eq "move_rel_up" )
{
        moveUp();
}
elsif ( $command eq "move_rel_down" )
{
        moveDown();
}
elsif ( $command eq "move_rel_left" )
{
        moveLeft();
}
elsif ( $command eq "move_rel_right" )
{
        moveRight();
}
elsif ( $command eq "move_rel_upleft" )
{
        moveUpLeft();
}
elsif ( $command eq "move_rel_upright" )
{
        moveUpRight();
}
elsif ( $command eq "move_rel_downleft" )
{
        moveDownLeft();
}
elsif ( $command eq "move_rel_downright" )
{
        moveDownRight();
}
elsif ( $command eq "preset_home" )
{
        setPanTilt( $pan_max / 2 , $tilt_max / 2);
}
else
{
        Error( "Can't handle command $command\n" );
}
It's pretty rough, but it's at least a starting point to get this great camera working under Zoneminder. Feel free to update / fix my bugs.

Disclaimer: My perl is very very very rusty, and I'm too impatient to fix all of the things that are missing. And there are a few of them.
negx
Posts: 8
Joined: Tue Jul 31, 2007 2:25 pm

Post by negx »

anybody tried this script out? does it work?
User avatar
cordel
Posts: 5210
Joined: Fri Mar 05, 2004 4:47 pm
Location: /USA/Washington/Seattle

Post by cordel »

Information you may want to know:
Things changed in ZM 1.23.x to a modular scheme so this script would need to be updated. It should work fine with any version previous.

As for as your actual question, I could not say as I don't have any equipment to test it with.
mgc8
Posts: 8
Joined: Sat Dec 20, 2008 1:32 am

New module for version 1.23.x

Post by mgc8 »

Hello,

I needed this working so I spent a few hours writing a module for the new 1.23.x versions. It is attached below.
Many thanks to zylantha for the original idea (I wasn't aware of the SYSFS mode of controlling the camera, it makes things a lot easier).

Code: Select all

# ==========================================================================
#
# ZoneMinder PWC Control Protocol Module, $Date: 2008-12-20 17:30:29 +0000 (Sat, 20 Dec 2008) $, $Revision: 01 $
# Copyright (C) 2003, 2004, 2005, 2006  Philip Coombes
# Copyright (C) 2008 Mihnea-Costin Grigore
#
# 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 PWC (Logitech Orbit MP, etc)
# control protocol
#
package ZoneMinder::Control::Pwc;

use 5.006;
use strict;
use warnings;

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

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

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

# ==========================================================================
#
# PWC Control Protocol
#
# ==========================================================================

use ZoneMinder::Debug qw(:all);
use ZoneMinder::Config qw(:all);

use Time::HiRes qw( usleep );

# Define variables to hold current pan and tilt position
my $current_pan  = 0;
my $current_tilt = 0;
my $control_device; 

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();
    
    $control_device = $self->{Monitor}->{ControlDevice};
    CORE::open(SYSFS, $control_device);
    ($current_pan, $current_tilt) = split(/\ /,<SYSFS>);
    CORE::close(SYSFS);

    Info ( "Initial pan/tilt: $current_pan $current_tilt" );
    
    $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;
    
    my $result = undef;

    $cmd = "$current_pan $current_tilt";
    printMsg( $cmd, "Tx" );

    Info( "Device: $control_device" );
    Info( "Command: $cmd" );

    CORE::open(SYSFS, ">$control_device");
    print SYSFS $cmd."\n";
    CORE::close(SYSFS);
    
    $result = !undef;

    return( $result );
}

sub moveRelUp
{
    my $self = shift;
    my $params = shift;
    my $tiltstep = $self->getParam( $params, 'tiltstep' );
    Debug( "Move Up" );
    $current_tilt += $tiltstep;
    $self->sendCmd();
}

sub moveRelDown
{
    my $self = shift;
    my $params = shift;
    my $tiltstep = $self->getParam( $params, 'tiltstep' );
    Debug( "Move Down" );
    $current_tilt -= $tiltstep;
    $self->sendCmd();
}

sub moveRelLeft
{
    my $self = shift;
    my $params = shift;
    my $panstep = $self->getParam( $params, 'panstep' );
    Debug( "Move Left" );
    $current_pan -= $panstep;
    $self->sendCmd();
}

sub moveRelRight
{
    my $self = shift;
    my $params = shift;
    my $panstep = $self->getParam( $params, 'panstep' );
    Debug( "Move Right" );
    $current_pan += $panstep;
    $self->sendCmd();
}

sub moveRelUpRight
{
    my $self = shift;
    my $params = shift;
    my $panstep = $self->getParam( $params, 'panstep' );
    my $tiltstep = $self->getParam( $params, 'tiltstep' );
    Debug( "Move Up Right" );
    $current_pan += $panstep;
    $current_tilt += $tiltstep;
    $self->sendCmd();
}

sub moveRelUpLeft
{
    my $self = shift;
    my $params = shift;
    my $panstep = $self->getParam( $params, 'panstep' );
    my $tiltstep = $self->getParam( $params, 'tiltstep' );
    Debug( "Move Up Left" );
    $current_pan -= $panstep;
    $current_tilt += $tiltstep;
    $self->sendCmd();
}

sub moveRelDownRight
{
    my $self = shift;
    my $params = shift;
#    my $key; my $value; while ( ($key, $value) = each (%{$params}) ) { Info ( "Params: $key=".$params->{$key} ); }
    my $panstep = $self->getParam( $params, 'panstep' );
    my $tiltstep = $self->getParam( $params, 'tiltstep' );
    Debug( "Move Down Right" );
    $current_pan += $panstep;
    $current_tilt -= $tiltstep;
    $self->sendCmd();
}

sub moveRelDownLeft
{
    my $self = shift;
    my $params = shift;
    my $panstep = $self->getParam( $params, 'panstep' );
    my $tiltstep = $self->getParam( $params, 'tiltstep' );
    Debug( "Move Down Left" );
    $current_pan -= $panstep;
    $current_tilt -= $tiltstep;
    $self->sendCmd();
}

sub moveMap
{
    my $self = shift;
    my $params = shift;
    my $width = $self->getParam( $params, 'width' );
    my $height = $self->getParam( $params, 'height' );
    my $xcoord = $self->getParam( $params, 'xcoord' );
    my $ycoord = $self->getParam( $params, 'ycoord' );
    my $panstep = $xcoord - ($width / 2);
    my $tiltstep = $ycoord - ($height / 2);
    Debug( "Move Map" );
    
    $current_pan += $panstep;
    $current_tilt -= $tiltstep;
    $self->sendCmd();
}

sub presetHome
{
    my $self = shift;
    Debug( "Home Preset" );
    $current_pan = 0;
    $current_tilt = 0;
    $self->sendCmd();
}

sub reset
{
    presetHome();
}

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

=head1 NAME

ZoneMinder::Database - Perl extension for blah blah blah

=head1 SYNOPSIS

  use ZoneMinder::Database;
  blah blah blah

=head1 DESCRIPTION

Stub documentation for ZoneMinder, created by h2xs. It looks like the
author of the extension was negligent enough to leave the stub
unedited.

Blah blah blah.

=head2 EXPORT

None by default.



=head1 SEE ALSO

Mention other useful documentation such as the documentation of
related modules or operating system documentation (such as man pages
in UNIX), or any relevant external documentation such as RFCs or
standards.

If you have a mailing list set up for your module, mention it here.

If you have a web site set up for your module, mention it here.

=head1 AUTHOR

Philip Coombes, E<lt>philip.coombes@zoneminder.comE<gt>

=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
Installation:

Copy the above into a file called "Pwc.pm" and place it in /usr/share/perl5/ZoneMinder/Control or alternatively in the appropriate folder on your installation if it is different (the one containing Visca.pm, PelcoD.pm and so on).

After that:
1. Create the new "Control Type" in the web interface with a name of your choosing, specifying Type=Local, Protocol=Pwc and only "Can reset".
2. "Move" tab -> everything except "Continuous" (I might be wrong about this, please correct me if that's the case).
3. "Pan" tab -> Min Range: -7000, Max Range: 7000, Min Step: 200, Max Step: 200, Has Pan Speed, Min/Max Speed=1
4. "Tilt" tab -> Min Range: -2500, Max Range: 2500, Min Step: 100, Max Step: 100, Has Tilt Speed, Min/Max Speed=1
5. Leave the other tabs unconfigured.
6. Back in the "Monitor" setup, specify "Control Device = /sys/class/video4linux/videoX/pan_tilt " where X="your device number". Choose the new Control Type you've just created and leave the rest unconfigured.
7. You may need a zoneminder restart here just to be safe.
8. Enjoy!

Additional Notes
- This is provided "as is" with no warranty, neither express nor implied etc.
- What works: resetHome; horizontal and vertical pan & tilt; clicking on the image to move in that direction (on the edges moves more, in the center moves less).
- What doesn't work: the rest :) mainly diagonal movement (for some strange reason the program does not send 'panstep' and 'tiltstep' parameters to the function, dunno why, maybe a bug? Also, I don't know how to check for the limits and apply them (right now, it will merrily send commands that go beyond the supported range, although it shouldn't have any effect on the hardware and you can recover via a resetHome).
- Future: Add rounding to the mapMove function so that small steps are not added to get weird values, and refine the other options. Hopefully see this added to the default distribution (as it used to be a quite popular cam before the newer UVC versions came up).

That about covers it, methinks...

Best regards and have fun,
Mihnea
erbal
Posts: 1
Joined: Tue Apr 07, 2009 4:07 pm

Post by erbal »

OK, I've been trying to get this working with my logitech sphere ... and while the tilt appears to work, the pan seems to have issues ... sometimes it works, others it doesnt.

I've been digging and found the pwc-if.c which seems to contain the code for the SYSFS device: (I'm including the code so that others dont need to go digging)

Code: Select all

static ssize_t store_pan_tilt(struct device *class_dev,
			      struct device_attribute *attr,
			      const char *buf, size_t count)
{
	struct pwc_device *pdev = cd_to_pwc(class_dev);
	int pan, tilt;
	int ret = -EINVAL;

	if (strncmp(buf, "reset", 5) == 0)
		ret = pwc_mpt_reset(pdev, 0x3);

	else if (sscanf(buf, "%d %d", &pan, &tilt) > 0)
		ret = pwc_mpt_set_angle(pdev, pan, tilt);

	if (ret < 0)
		return ret;
	return strlen(buf);
}
the pwc_mpt_set_angle appears to be defined in pwc-ctrl.c:

Code: Select all

int pwc_mpt_set_angle(struct pwc_device *pdev, int pan, int tilt)
{
	int ret;

	/* check absolute ranges */
	if (pan  <pdev>angle_range.pan_min  ||
	    pan  > pdev->angle_range.pan_max  ||
	    tilt <pdev>angle_range.tilt_min ||
	    tilt > pdev->angle_range.tilt_max)
		return -ERANGE;

	/* go to relative range, check again */
	pan  -= pdev->pan_angle;
	tilt -= pdev->tilt_angle;
	/* angles are specified in degrees * 100, thus the limit = 36000 */
	if (pan <36000> 36000 || tilt <36000> 36000)
		return -ERANGE;

	ret = _pwc_mpt_set_angle(pdev, pan, tilt);
	if (ret >= 0) {
		pdev->pan_angle  += pan;
		pdev->tilt_angle += tilt;
	}
	if (ret == -EPIPE) /* stall -> out of range */
		ret = -ERANGE;
	return ret;
}
According to the code doing the actual setting of the position, the ranges are +- 360 * 100, the limits are defined in pwc-if.c in the usb_pwc_probe method:

Code: Select all

if (vendor_id == 0x046D && product_id == 0x08B5)
	{
		/* Logitech QuickCam Orbit
		   The ranges have been determined experimentally; they may differ from cam to cam.
		   Also, the exact ranges left-right and up-down are different for my cam
		  */
		pdev->angle_range.pan_min  = -7000;
		pdev->angle_range.pan_max  =  7000;
		pdev->angle_range.tilt_min = -3000;
		pdev->angle_range.tilt_max =  2500;
	}
I would take a stab in the dark that the ranges have been obtained by looking at the usage of setpwc, which correctly uses +-7000 (although on my cam I've found sending it to 7000 can make it lock up as though it's going out of range)

Does anyone have any experience of compiling & installing v4l from source ... I'm a linux noob, I can just about install xubuntu & install a few apps, but that's about as far as it goes - I am however a coder, so I can read the code and find issues, I just dont have a clue about how to go about applying the changes!

I could do with a few pointers here ... Help!

PS: I suspect I may be looking at the wrong versions of the files, however I've not yet found anywhere to download the current source of v4l, the site seems to pull you towards binaries not the source.
Post Reply