The Trendnet TV-IP400 is a fun little camera that will work great for home surveillance, as a controllable web cam, etc. The only problem is that the software that comes with it really sucks. I reverse engineered the protocol that is used to control the camera, and wrote a ZoneMinder driver for it… go here for more information. This follow up describes the control protocol that I reverse engineered, and contains most information that you’ll need if you want to write your own driver. I’m assuming that you are familiar with HTTP requests and that you know how to send GET and POST requests to an IP address.

Feel free to use the information in this page in any way you want. I make no guarantees as to its accuracy or completeness. If you write any software based on this info, please let me know!

control commands:

The following CGIs are supported.

/PANTILTCONTROL.CGI

This is the main CGI for camera movement control, and should always be a POST request. The commands are very simple, there’s one parameter for specifying the direction of movement, and both for horizontal and vertical movement you need to set the degree at which it should move. This degree should be an integer of 1 or higher. (The web control interface never submits values higher than 10, and I haven’t tested any either).
I don’t think these commands can be used to do absolute movement.
Theoretically, if you move 3 degrees up and 3 degrees left, then 5 degrees up and 9 degrees right, then 8 degrees down and 6 degrees left, you should be exactly at the point you started - I’m not sure how accurately this would work, but you might be pleasantly surprised. (preset positions don’t seem to wander over time, so moving relative distances might be accurate too).

On to the commands now. To move the camera 1 degree up and 1 left, you should send the following command in the POST body:

PanSingleMoveDegree=1&TiltSingleMoveDegree=1&PanTiltSingleMove=1

To send the camera 3 degrees to the right, send the following:

PanSingleMoveDegree=3&TiltSingleMoveDegree=0&PanTiltSingleMove=5

I hope you’re catching on. Here’s the complete table of movement directions that should be set with the PanTiltSingleMove parameter:

0 up left
1 up
2 up right
3 left
4 home
5 right
6 down left
7 down
8 down right

If you’re just tilting the camera (vertically), you can leave out the PanSingleMoveDegree parameter. Likewise, if you’re only panning the camera (horizontally), you can leave out the TiltSingleMoveDegree parameter. If you’re sending the camera to the Home position, you can leave out both.

Note that there’s no way to control the zoom! The IP400’s zoom is digital only. The java/activex interface and windows application all implement the zoom on the client side; the camera itself doesn’t know how to do this. So if you need the zoom (or the suggestion of a zoom), you’ll have to implement this yourself by cropping and resize the center of the video stream.

SWING MODES:

There are two swing modes that can be activated simply by passing the PanTiltSwingMode parameter and value to PANTILTCONTROL.CGI. The first scan mode will simply scan the room horizontally by panning it one step in every approx 1 second intervals until it reaches it’s left or right limit, then reverse direction. The swing mode will simply scan all preset positions and hold each position for a few seconds. I don’t think there’s any way to change the interval time for either swing mode, at least the decompiled camera java applet didn’t reveal any such options to me.

values for swing mode:

0 stop
1 horizontal scan, move every second
2 scan all preset positions

example:
to put the camera into horizontal scan mode, send:

PanTiltSwingMode=2

PRESET POSITIONS:

To move to a preset position, send the following command:

PanTiltPresetPositionMove=3

substitute 3 for the position number that you want to move to. The camera supports 25 preset positions, 0 to 24.

To clear a position, send:

ClearPosition=12

this would clear position 12 (which is the 13th preset)

To set a preset position:

send the following command.

PanTiltHorizontal=a&PanTiltVertical=b&SetName=myPosition&SetPosition=12

Where a and b would be the absolute values for the horizonal resp. vertical positions. There is some numbers juggling going on in the java applet of the webcam control application, and I haven’t made any attempts yet to reverse engineer this. All movement (except for movement to preset positions) is relative with this camera, and I am not sure that an absolute position can be calculated accurately from relative movements. As far as I know there’s only one way to get the current absolute position, which is actually through the mjpeg cgi, see below.

———————————–
/IOCONTROL.CGI

It appears that this CGI can be called to trigger the camera to upload or email a current camera snapshot. I only know this from decompiling the xplug class, and haven’t played around with it at at all.
The parameter string should look like this (or at least that’s how the java applet sends it)

Trigger1=0&Trigger2=1&ImageUpload=0&ImageEmail=1

where values of all 4 parameters should be either 0 or 1. You should call the CGI with the POST method, however, if you use the GET method (eg by simply entering the CGI url in your browser), it will answer with the current values of these parameters, like this:

Trigger1=0&Trigger2=0&ImageUpload=0&ImageEmail=0&CurrentTime=2007-08-16 15:27:34

(There’s no point in trying to set those properties in the GET request (with something like /IOCONTROL.CGI?ImageUpload=1) as the IP400 seems to ignore those: the camera apparently needs a POST request to receive parameters. )

/MJPEG.CGI
/VIDEO.CGI

(use GET request)
Both these urls appear to return a MJPEG stream, which is basically just an endless stream of jpeg images with a short header in between. That header data looks like this:

--video boundary--Content-length: 10089
Date: 2007-08-16 14:56:19 IO_00000000_PT_136_046
Content-type: image/jpeg

but the ‘Date’ header isn’t present when using the VIDEO.CGI variant. The interesting thing about this Date header is that the last bit, the PT_136_046 actually indicates the current absolute Pan/Tilt position. The Ruby driver I’ve written for this camera can grab those coordinates from the stream, which is really only useful when you want to set a preset position.

You should be able to view the stream in a browser simply by using the address http://192.168.0.50/MJPEG.CGI (substitute the correct IP address of course).

Other than the missing Date header in the VIDEO.CGI response, there don’t seem to be any differences between these two streams. If you find any, please let me know.

By the way, the exact request that Trendnet’s xplug java applet sends to get the video stream is:

GET /MJPEG.CGI HTTP/1.0\r\n
User-Agent: user\r\n
Authorization: Basic abcdefgrn\r\n

where the authentication token is optional (but needed when you have user access control enabled in the camera). This token abcdefg should be the base64 encoded string of ‘username:password’ as is standard for http basic authentication. It’s probably best if your request look exactly the same, and do make sure you end those headers with double carriage return and line feed, as illustrated.

/IMAGE.JPG

simply returns a jpeg of the current camera view, which is handy if you want to save a snapshot or display on clients that can’t handle streaming video.

If you need to grab a bunch of frames, want to convert the stream to another format or do any other magic with your camera view, I recommend you look at ffmpeg and imagemagick.

Here’s an example of how to save the first image from the mjpeg stream to an image:

ffmpeg -f mjpeg -i http://192.168.0.50/MJPEG.CGI -vframes 1 -an -s 320x240 cam%d.jpg

It will save multiple jpgs if you increase the vframes value, but if you want to do that it would be better to output to another video format then to output to jpg.

Having written all this: you may not need to know any of this since I’ve written two drivers, one Ruby and one ZoneMinder driver that can be used on any platform that supports Perl or Ruby. I’ll post the code for the ruby driver as soon as it’s ready.