Controlling a Logitech Sphere pan/tilt with Motion.
To be able to control the Logitech Sphere you need one of the latest versions of the pwc driver.
And you also need a recent version of Motion. I recommend the latest version (at the moment of writing this it is 3.1.19).
I added a Motion 3.2.1 version below where you do not need XMLRPC -- KennethLavrsen - 28 May 2005
And finally you need to install the XML-RPC-C library because the pan/tilt is controlled via the XML-RPC interface of Motion using the motion-control program.
Now we get to the fun part. Controlling it from a webpage.
First we need a webpage which shows the live image using the cambozola applet.
Let us call this file
livecam.htm
<head> Webcam - Pan and Tilt </head>
<body text="#000000" bgcolor="#EEF0E0" link="#0000EE" vlink="#551A8B" alink="#FF0000">
---++ Web Camera - Logitech Sphere - Pan and Tilt
<applet code=com.charliemouse.cambozola.Viewer
archive=cambozola.jar width=320 height=240>
<param name=url value=http://www.yourdomaine.com:8081> </applet>
<form name="pantilt" action="control204pt.php" method="post"> Click buttons below to rotate and tilt camera.
<INPUT TYPE=SUBMIT NAME="command" VALUE="Pan Left ">
<INPUT TYPE=SUBMIT NAME="command" VALUE=" L5 ">
<INPUT TYPE=SUBMIT NAME="command" VALUE=" L4 ">
<INPUT TYPE=SUBMIT NAME="command" VALUE=" L3 ">
<INPUT TYPE=SUBMIT NAME="command" VALUE=" L2 ">
<INPUT TYPE=SUBMIT NAME="command" VALUE=" L1 "> <INPUT TYPE=SUBMIT NAME="command" VALUE="Center Pan">
<INPUT TYPE=SUBMIT NAME="command" VALUE=" R1 "> <INPUT TYPE=SUBMIT NAME="command" VALUE=" R2 ">
<INPUT TYPE=SUBMIT NAME="command" VALUE=" R3 "> <INPUT TYPE=SUBMIT NAME="command" VALUE=" R4 ">
<INPUT TYPE=SUBMIT NAME="command" VALUE=" R5 "> <INPUT TYPE=SUBMIT NAME="command" VALUE="Pan Right">
<INPUT TYPE=SUBMIT NAME="command" VALUE="Tilt Down"> <INPUT TYPE=SUBMIT NAME="command" VALUE=" D5 ">
<INPUT TYPE=SUBMIT NAME="command" VALUE=" D4 "> <INPUT TYPE=SUBMIT NAME="command" VALUE=" D3 ">
<INPUT TYPE=SUBMIT NAME="command" VALUE=" D2 "> <INPUT TYPE=SUBMIT NAME="command" VALUE=" D1 ">
<INPUT TYPE=SUBMIT NAME="command" VALUE="Center Tilt"> <INPUT TYPE=SUBMIT NAME="command" VALUE=" U1 ">
<INPUT TYPE=SUBMIT NAME="command" VALUE=" U2 "> <INPUT TYPE=SUBMIT NAME="command" VALUE=" U3 ">
<INPUT TYPE=SUBMIT NAME="command" VALUE=" U4 "> <INPUT TYPE=SUBMIT NAME="command" VALUE=" U5 ">
<INPUT TYPE=SUBMIT NAME="command" VALUE=" Tilt Up ">
<br /> <INPUT TYPE=SUBMIT NAME="command" VALUE="Calibrate">
</form>
</body>
The example has buttons for both relative movement and absolute positions. After a while the Logitech Sphere does becomes offset from where it is supposed to be. By pressing the calibrate button the camera is brought to its extreme positions and that calibrates the absolute position again.
We do not want the page to reload each time we press a button. That looks awful. We just the current page to stay without being refreshed or replaced, and the live stream to continue showing the live rotating camera.
For this we use a little trick. When we submit the form it calls a PHP program called
control204pt.php
. The same thing could have been written as a perl CGI program but I chose PHP in this case.
When
control204pt.php
is called it immediately returns HTTP error 204 to the browser. This error means "No Content. The requested completed successfully but the resource requested is empty (has zero length)".
This makes the browser stay with the current page.
In reality the
control204pt.php
continues doing more on the server. Based on the data submitted in the form inside
livecam.htm
it sends XML-RPC commands directly to Motion telling it to rotate the Logitech Sphere.
Smart. Isn't it? Here is the code for
control204pt.php
. It assumes that motion-control is is /usr/local/bin and that the Apache user is allowed to execute it.
<? Header("HTTP/1.0 204 No Content");
$command = (IsSet($_REQUEST['command'])) ? $_REQUEST['command'] : "";
switch ($command) {
case " L5 ": exec("/usr/local/bin/motion-control track pan 8 -200 &"); break;
case " L4 ": exec("/usr/local/bin/motion-control track set 8 -60 1000 &"); break;
case " L3 ": exec("/usr/local/bin/motion-control track set 8 -45 1000 &"); break;
case " L2 ": exec("/usr/local/bin/motion-control track set 8 -30 1000 &"); break;
case " L1 ": exec("/usr/local/bin/motion-control track set 8 -15 1000 &"); break;
case "Center Pan": exec("/usr/local/bin/motion-control track set 8 0 1000 &"); break;
case " R1 ": exec("/usr/local/bin/motion-control track set 8 15 1000 &"); break;
case " R2 ": exec("/usr/local/bin/motion-control track set 8 30 1000 &"); break;
case " R3 ": exec("/usr/local/bin/motion-control track set 8 45 1000 &"); break;
case " R4 ": exec("/usr/local/bin/motion-control track set 8 60 1000 &"); break;
case " R5 ": exec("/usr/local/bin/motion-control track pan 8 200 &"); break;
case " U5 ": exec("/usr/local/bin/motion-control track tilt 8 100 &"); break;
case " U4 ": exec("/usr/local/bin/motion-control track set 8 1000 20 &"); break;
case " U3 ": exec("/usr/local/bin/motion-control track set 8 1000 15 &"); break;
case " U2 ": exec("/usr/local/bin/motion-control track set 8 1000 10 &"); break;
case " U1 ": exec("/usr/local/bin/motion-control track set 8 1000 5 &"); break;
case "Center Tilt": exec("/usr/local/bin/motion-control track set 8 1000 0 &"); break;
case " D1 ": exec("/usr/local/bin/motion-control track set 8 1000 -5 &"); break;
case " D2 ": exec("/usr/local/bin/motion-control track set 8 1000 -10 &"); break;
case " D3 ": exec("/usr/local/bin/motion-control track set 8 1000 -15 &"); break;
case " D4 ": exec("/usr/local/bin/motion-control track set 8 1000 -20 &"); break;
case " D5 ": exec("/usr/local/bin/motion-control track tilt 8 -100 &"); break;
case "Pan Left ": exec("/usr/local/bin/motion-control track pan 8 -10 &"); break;
case "Pan Right": exec("/usr/local/bin/motion-control track pan 8 10 &"); break;
case " Tilt Up ": exec("/usr/local/bin/motion-control track tilt 8 5 &"); break;
case "Tilt Down": exec("/usr/local/bin/motion-control track tilt 8 -5 &"); break;
case "Calibrate": exec("/usr/local/bin/motion-control conf set 8 text_left \"CAMERA CALIBRATING POSITION\" &");
exec("/usr/local/bin/motion-control detection pause 8 &");
exec("/usr/local/bin/motion-control track set 8 -69 24 &"); sleep(5);
exec("/usr/local/bin/motion-control track set 8 69 -29 &"); sleep(3);
exec("/usr/local/bin/motion-control track set 8 65 -5 &"); sleep(2);
exec("/usr/local/bin/motion-control conf set 8 text_left \"CAMERA\" &");
exec("/usr/local/bin/motion-control detection resume 8 &");
default: break; } ?>
--
KennethLavrsen - 12 Sep 2004
Here is the version of
control204pt.php
used for Motion 3.2.1 and later.
<? Header("HTTP/1.0 204 No Content");
$command = (IsSet($_REQUEST['command'])) ? $_REQUEST['command'] : "";
switch ($command) {
case " L5 ": readfile('http://username:password@localhost:8080/9/track/set?pan=-200&tilt=0'); break;
case " L4 ": readfile('http://username:password@localhost:8080/9/track/set?x=-60&y=1000'); break;
case " L3 ": readfile('http://username:password@localhost:8080/9/track/set?x=-45&y=1000'); break;
case " L2 ": readfile('http://username:password@localhost:8080/9/track/set?x=-30&y=1000'); break;
case " L1 ": readfile('http://username:password@localhost:8080/9/track/set?x=-15&y=1000'); break;
case "Center Pan": readfile('http://username:password@localhost:8080/9/track/set?x=0&y=1000'); break;
case " R1 ": readfile('http://username:password@localhost:8080/9/track/set?x=15&y=1000'); break;
case " R2 ": readfile('http://username:password@localhost:8080/9/track/set?x=30&y=1000'); break;
case " R3 ": readfile('http://username:password@localhost:8080/9/track/set?x=45&y=1000'); break;
case " R4 ": readfile('http://username:password@localhost:8080/9/track/set?x=60&y=1000'); break;
case " R5 ": readfile('http://username:password@localhost:8080/9/track/set?pan=200&tilt=0'); break;
case " U5 ": readfile('http://username:password@localhost:8080/9/track/set?pan=0&tilt=100'); break;
case " U4 ": readfile('http://username:password@localhost:8080/9/track/set?x=1000&y=20'); break;
case " U3 ": readfile('http://username:password@localhost:8080/9/track/set?x=1000&y=15'); break;
case " U2 ": readfile('http://username:password@localhost:8080/9/track/set?x=1000&y=10'); break;
case " U1 ": readfile('http://username:password@localhost:8080/9/track/set?x=1000&y=5'); break;
case "Center Tilt": readfile('http://username:password@localhost:8080/9/track/set?x=1000&y=0'); break;
case " D1 ": readfile('http://username:password@localhost:8080/9/track/set?x=1000&y=-5'); break;
case " D2 ": readfile('http://username:password@localhost:8080/9/track/set?x=1000&y=-10'); break;
case " D3 ": readfile('http://username:password@localhost:8080/9/track/set?x=1000&y=-15'); break;
case " D4 ": readfile('http://username:password@localhost:8080/9/track/set?x=1000&y=-20'); break;
case " D5 ": readfile('http://username:password@localhost:8080/9/track/set?pan=0&tilt=-100'); break;
case "Pan Left ": readfile('http://username:password@localhost:8080/9/track/set?pan=-10&tilt=0'); break;
case "Pan Right": readfile('http://username:password@localhost:8080/9/track/set?pan=10&tilt=0'); break;
case " Tilt Up ": readfile('http://username:password@localhost:8080/9/track/set?pan=0&tilt=5'); break;
case "Tilt Down": readfile('http://username:password@localhost:8080/9/track/set?pan=0&tilt=-5'); break;
case "Calibrate":
readfile('http://username:password@localhost:8080/9/config/set?text_left=CAMERA%20CALIBRATING%20POSITION');
readfile('http://username:password@localhost:8080/9/detection/pause');
readfile('http://username:password@localhost:8080/9/track/set?x=-69&y=24'); sleep(5);
readfile('http://username:password@localhost:8080/9/track/set?x=69&y=-29'); sleep(3);
readfile('http://username:password@localhost:8080/9/track/set?x=65&y=-5'); sleep(2);
readfile('http://username:password@localhost:8080/9/config/set?text_left=CAMERA');
readfile('http://username:password@localhost:8080/9/detection/start');
default: break; } ?>
--
KennethLavrsen - 28 May 2005
Here is a simple page that allows the viewer to click on the picture to move the camera to that point. It also only takes a snapshot when the page is fetched.
<head> Webcam <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<meta http-equiv="Refresh" content="3; URL=index.php"> </head>
<!--
<?
$command = (IsSet($_REQUEST['command'])) ? $_REQUEST['command'] : "";
switch ($command)
{
case "Home":
readfile('http://localhost:8080/0/track/set?x=0&y=0');
break;
case "Calibrate":
readfile('http://localhost:8080/0/detection/pause');
readfile('http://localhost:8080/0/track/set?x=-69&y=24');
sleep(5);
readfile('http://localhost:8080/0/track/set?x=69&y=-29');
sleep(3);
readfile('http://localhost:8080/0/track/set?x=0&y=0');
sleep(2);
readfile('http://localhost:8080/0/detection/start');
break;
case "Auto-Track On":
readfile('http://localhost:8080/0/track/auto?value=1');
readfile('http://localhost:8080/0/config/set?text_left=Auto-Track');
break;
case "Auto-Track Off":
readfile('http://localhost:8080/0/track/auto?value=0');
readfile('http://localhost:8080/0/config/set?text_left=');
break;
default:
break;
}
$x = (IsSet($_REQUEST['x'])) ? $_REQUEST['x'] - 320 : "";
$y = (IsSet($_REQUEST['y'])) ? $_REQUEST['y'] - 240 : "";
if (IsSet($_REQUEST['x']) and IsSet($_REQUEST['y'])) {
$x = (int)($x / 15);
$y = (int)(-$y / 15);
readfile('http://localhost:8080/0/track/set?pan='.$x.'&tilt='.$y);
sleep(1);
}
readfile('http://localhost:8080/0/action/snapshot');
sleep(1);
?>
-->
<body> Click on image to pan camera. <form action="" method="get">
<input type="image" src="lastsnap.jpg" width="640" height="480" border="0"/>
<input type="submit" name="command" value="Calibrate"/>
<input type="submit" name="command" value="Home"/>
<input type="submit" name="command" value="Auto-Track On"/>
<input type="submit" name="command" value="Auto-Track Off"/>
</form> </body>
--
DeanCording - 20 Apr 2006
You can modify the cambozola java applet to enable Dean's click-to-center feature. First integrate his x/y parameter handling into Kenneth's control204pt.php file. Then make these changes to com.charliemouse.cambozola.Viewer:
--- orig/cambozola-0.68/src/com/charliemouse/cambozola/Viewer.java 2006-02-19 11:35:06.000000000
-0800 +++ ./src/com/charliemouse/cambozola/Viewer.java 2007-06-22 16:12:03.000000000
-0700 @@ -38,6 +38,8 @@
import java.awt.image.ImageObserver; import java.awt.image.ImageProducer; import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL; import java.util.Enumeration;
@@ -54,6 +56,7 @@
private static final String PAR_DELAY = "delay";
private static final String PAR_RETRIES = "retries";
private static final String PAR_URL = "url";
+ private static final String PAR_CONTROLURL = "controlurl";
private static final String PAR_ACCESSORIES = "accessories";
private static final String PAR_WATERMARK = "watermark";
private static final String PAR_ACCESSORYSTYLE = "accessorystyle";
@@ -68,6 +71,7 @@ private URL m_documentBase = null;
private URL m_codeBase = null; private URL m_mainURL = null;
+ private URL m_controlURL = null; private Vector m_alternateURLs = null;
private CamStream m_imgStream = null;
private String m_msg = null;
@@ -107,6 +111,7 @@
m_parameters.put(PAR_DELAY, getHTMLParameterValue(PAR_DELAY));
m_parameters.put(PAR_RETRIES, getHTMLParameterValue(PAR_RETRIES));
m_parameters.put(PAR_URL, getHTMLParameterValue(PAR_URL));
+ m_parameters.put(PAR_CONTROLURL, getHTMLParameterValue(PAR_CONTROLURL));
m_parameters.put(PAR_ACCESSORIES, getHTMLParameterValue(PAR_ACCESSORIES));
m_parameters.put(PAR_WATERMARK, getHTMLParameterValue(PAR_WATERMARK));
m_parameters.put(PAR_WATERMARK, getHTMLParameterValue("watermarks"));
@@ -188,6 +193,16 @@ reportError(mfe); } }
+ //
+ m_controlURL = null; + String controlurl = getParameterValue(PAR_CONTROLURL);
+ if (controlurl != null && !controlurl.equals("")) { + try {
+ m_controlURL = new URL(m_codeBase,controlurl);
+ } catch (MalformedURLException mfe) {
+ reportError(mfe);
+ }
+ }
setCurrentURL(m_mainURL);
setAlternateURLs(m_alternateURLs); configureAccessories(getParameterValue(PAR_ACCESSORIES));
@@ -234,6 +249,8 @@
cv.m_parameters.put(PAR_RETRIES, arg.substring(eqidx)); }
else if (arg.startsWith("-delay=")) { cv.m_parameters.put(PAR_DELAY, arg.substring(eqidx));
+ } else if (arg.startsWith("-controlurl=")) {
+ cv.m_parameters.put(PAR_CONTROLURL, arg.substring(eqidx)); }
else if (arg.startsWith("-failureimage=")) { cv.m_parameters.put(PAR_FAILUREIMAGE, arg.substring(eqidx)); }
else if (arg.startsWith("-watermark=") || arg.startsWith("-watermarks=")) {
@@ -664,6 +681,9 @@
public void mouseClicked(MouseEvent me) {
+ int mx = me.getX();
+ int my = me.getY();
+ if (!ms_standalone && !isDisplayingAccessories() && m_wmHit != null) {
// @@ -672,23 +692,41 @@ displayURL(m_wmHit.getURL(), null); return; }
- if (me.getX() >= Accessory.BUTTON_SIZE)
+ if (mx >= Accessory.BUTTON_SIZE) {
+ if(m_controlURL != null) postCoords( mx, my ); return; } - int idx = (int)(me.getY()/Accessory.BUTTON_SIZE);
+ int idx = (int)(my/Accessory.BUTTON_SIZE);
if (idx >= m_accessories.size()) { // System.err.println("Out of range for accessories");
+ if(m_controlURL != null) postCoords( mx, my ); } else { // // Get the local location... //
- Point p = new Point(me.getX(), me.getY() - (idx * Accessory.BUTTON_SIZE));
+ Point p = new Point(mx, my - (idx * Accessory.BUTTON_SIZE));
((Accessory)(m_accessories.elementAt(idx))).actionPerformed(p); } }
+ private void postCoords(int x, int y)
+ {
+ try {
+ HttpURLConnection connection = (HttpURLConnection) m_controlURL.openConnection();
+ connection.setRequestMethod("POST");
+ connection.setDoOutput(true);
+ PrintWriter out = new PrintWriter(connection.getOutputStream());
+ String name = "x=" + x + "&y=" + y;
+ out.println(name);
+ out.close();
+ // Force a response + int code = connection.getResponseCode();
+ } catch( Exception e ) {
+ throw new RuntimeException( "Could not post: "
+ e.getMessage() );
+ }
+ }
public void mousePressed(MouseEvent me) {
Run 'ant clean' and 'ant dist' to make a new jar. Now you can add
<param name="controlurl" value="http://MYWEBSITE/control204pt.php"/>
and move your camera via the applet. Keep in mind that Dean's method assumes a 640 x 480 frame. So this patch and the control PHP needs to be modified if you're using a different size. Perhaps cambozola should send x/y as a positive or negative percent. Then the control file could make the necessary adjustments regardless of the picture size.
--
JoelRosenberg - 22 Jun 2007
Feel welcome to extend the document above or simply add a comment by using the text field and the button below.
Do these instructions apply to the UVC powered 2006 Logitech Quickcam Orbit MPs (046d:08cc)? It seems that the older Oribts/Spheres can be powered by the PWC driver and hence will work with the patch, but the newer cams can't take advantage of the code above... any suggestions?
--
JerMe - 07 Aug 2007
JerMe do you run Motion 3.2.8?
--
KennethLavrsen - 07 Aug 2007
This is most excellent. I have been searching for a while for software this versatile in motion detection, remote control of pan/tilt, and auto-tracking. VERY nicely done. I have an Orbit MP, and am finally able to use its features the way I wanted all along.
Thank you
--
JimHoward - 13 Sep 2007
Now with the latest motion SVN, latest uvc and latest libwebcam, I once again cannot get the pan/tilt to work. I get the error:
Apr 30 21:17:15 host motion: [0] uvc_move: Failed to reset UVC camera to starting position! Reason: Invalid argument
uvcdynctrl seems to see all the functions fine:
uvcdynctrl -c Listing available controls for device video0: Raw bits per pixel Disable video processing LED1 Frequency LED1 Mode Pan/tilt Reset Tilt Reset Pan Reset Tilt (relative) Pan (relative) Exposure, Auto Priority Exposure (Absolute) Exposure, Auto Backlight Compensation Sharpness White Balance Temperature Power Line Frequency Gain White Balance Temperature, Auto Saturation Contrast Brightness
uvcdynctrl -l Listing available devices: video0 UVC Camera (046d:08cc)
Some of the functions work, and some do not. The tilt reset, for example, seems to work, but the relative pan/tilt functions do not.
At this point, I may have a uvc issue or a libwebcam issue. Some of the uvcdynctrl commands return:
uvcdynctrl -g 'Tilt (relative)' ERROR: Unable to retrieve control value: A
Video4Linux2 API call returned an unexpected error 22. (Code: 12)
But I will take any advice anyone can provide. Thank you
--
JimHoward - 01 May 2009
I just spent a few more hard hours on this, poured over the information here, the information in libwebcam, and the uvc driver. My summary of findings, and corresponding fix, was to change the information in the motion file track.h to match that in the logitech.xml udev file, and recompile motion. After doing so, my pan/tilt/etc works just great.
The version of the logitech.xml file is .17 now, and claims to have the IDs fixed. I therefore changed the motion header to match the xml file instead of changing the xml file. At this point, motion's header file was the odd man out. uvc, libwebcam, etc had the values in the logitech.xml.
-Enjoy!
--
JimHoward - 01 May 2009
Hi there!
I just compiled and installed the cvs version of uvcvideo and libwebcam and I've got the exact same problem with my Logitech Quickcam Sphere AF:
uvcdynctrl -g 'Tilt (relative)' ERROR: Unable to retrieve control value: A
Video4Linux2? API call returned an unexpected error 22. (Code: 12)
Please, Mr. Howard, could you elaborate on what exactly you changed in track.h to get pan/tilt to work?
I don't quite understand what I'm looking at, yet
Thanks!
Jost
--
JostM - 07 Sep 2009
Hi,
I tried the aboves hacks with my linuxbox but I'm still unable to use tracking features with my logitech Orbit SPhere MP (2006).
I also tried to change track.h with no success. Here is the code :
#ifndef V4L2_CID_PAN_RELATIVE
/*#define V4L2_CID_PAN_RELATIVE (V4L2_CID_PRIVATE_BASE+7) */
#define V4L2_CID_PAN_RELATIVE 0x08000007
#endif
#ifndef V4L2_CID_TILT_RELATIVE
/*#define V4L2_CID_TILT_RELATIVE (V4L2_CID_PRIVATE_BASE+8) */
#define V4L2_CID_TILT_RELATIVE 0x08000008
#endif
#ifndef V4L2_CID_PANTILT_RESET
/*#define V4L2_CID_PANTILT_RESET (V4L2_CID_PRIVATE_BASE+9) */
#define V4L2_CID_PANTILT_RESET 0x08000009
#endif
JimHoward : Can you publish your code ?
Best regards,
--
BertrandDHerouville - 20 Oct 2009
I just had some success modifying the track.h file to include the logitech specific PAN/TILT/RESET values:
This feels very "Hackish" but at least it is working for me
track.h:124 ------
#ifndef V4L2 _CID_PAN_RELATIVE
//#define V4L2 _CID_PAN_RELATIVE (V4L2 _CID_PRIVATE_BASE+7)
#define V4L2 _CID_PAN_RELATIVE 0x009A0904
#endif
#ifndef V4L2 _CID_TILT_RELATIVE
//#define V4L2 _CID_TILT_RELATIVE (V4L2 _CID_PRIVATE_BASE+8)
#define V4L2 _CID_TILT_RELATIVE 0x009A0905
#endif
#ifndef V4L2 _CID_PANTILT_RESET
//#define V4L2 _CID_PANTILT_RESET (V4L2 _CID_PRIVATE_BASE+9)
#define V4L2 _CID_PANTILT_RESET 0x0A046D03
#endif
track.h ---------
--
ChrisBrinker - 12 Nov 2009
Where is track.h?
--
MattThornton - 01 Mar 2010
Is this also possible with a Raspberry Version 2?
I don´t know how to install the pwc driver....
--
SgtBlack - 12 Jun 2015