I just started learning C++ in earnest about a month ago. This weekend I felt like I knew enough to start looking at Python C extensions, so I wrote one to control the pan and tilt functions of my Logitech Orbit. I used this camera previously for my OLPC telepresence project.

There’s already a similar module out there, called lpantilt, that does this using Cython. But, I wanted to take a crack at it myself and do it with straight C.

#include <Python.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include "linux/videodev2.h"
#include "uvcvideo.h"
static int pantilt(int pan, int tilt, int reset) {
 struct v4l2_ext_control xctrls[2];
 struct v4l2_ext_controls ctrls;
 if (reset) {
   xctrls[0].id = V4L2_CID_PAN_RESET;
   xctrls[0].value = 1;
   xctrls[1].id = V4L2_CID_TILT_RESET;
   xctrls[1].value = 1;
 } else {
   xctrls[0].id = V4L2_CID_PAN_RELATIVE;
   xctrls[0].value = pan;
   xctrls[1].id = V4L2_CID_TILT_RELATIVE;
   xctrls[1].value = tilt;
 }
 ctrls.count = 2;
 ctrls.controls = xctrls;
 int fd;
 if (-1 == (fd = open("/dev/video0", O_RDWR))) {
   PyErr_SetString(PyExc_IOError, "Couldn't open /dev/video0.");
   return 0;
 }
 if (-1 == ioctl(fd, VIDIOC_S_EXT_CTRLS, &ctrls)) {
   PyErr_SetString(PyExc_IOError, "ioctl failed.");
   return 0;
 }
 if (-1 == close(fd)) {
   PyErr_SetString(PyExc_IOError, "Failed to close /dev/video0.");
   return 0;
 }
 return 1;
}
static PyObject* pantilt_reset(PyObject* self, PyObject* args) {
 if (!PyArg_ParseTuple(args, "")) {
   return NULL;
 }
 if (!pantilt(0, 0, 1)) {
   return NULL;
 }
 Py_RETURN_NONE;
}
static PyObject* pantilt_pantilt(PyObject* self, PyObject* args) {
 int pan;
 int tilt;
 if (!PyArg_ParseTuple(args, "ii", &pan, &tilt)) {
   return NULL;
 }
 if (!pantilt(pan * 64, tilt * 64, 0)) {
   return NULL;
 }
 Py_RETURN_NONE;
}
static PyMethodDef PantiltMethods[] = {
 {"pantilt", pantilt_pantilt, METH_VARARGS, "Set relative pan and tilt of the camera."},
 {"reset", pantilt_reset, METH_VARARGS, "Reset the pan and tilt of the camera."},
 {NULL, NULL, 0, NULL}
};
PyMODINIT_FUNC initpantilt(void) {
 (void) Py_InitModule("pantilt", PantiltMethods);
}

And here is the associated setup.py script to build it:

from distutils.core import Extension
from distutils.core import setup
m = Extension('pantilt', sources=['pantilt.c'])
setup(name='pantilt',
 version='1.0',
 description='Control pan and tilt of supported webcams.',
 ext_modules=[m])

To get this to build and run on Ubuntu, I had to:

  • Download and install libwebcam.
  • Execute uvcdynctrl -i logitech.xml ( logitech.xml can be found in the source for libwebcam).
  • Install the linux-source-* package and extract the /usr/src/linux-source*.tar.bz2 to disk.
  • Copy uvcvideo.h in to the same directory as pantilt.c .
  • Execute python setup.py install

Finally, here’s a sample usage of the pantilt module:

import pantilt, time
pantilt.reset()  # Reset the pan and tilt to the origin.
time.sleep(1)
pantilt.pantilt(10, 0)  # Increase relative pan by 10 degrees.
pantilt.pantilt(0, 10)  # Increase relative tilt by 10 degrees.