PTZ controls in Zoneminder for Vstarcam C7823WIP camera

Ok, there is no “Z” in this camera, so I’ve mapped to Zoneminder only Pan/Tilt. I think it was a dirty way. Maybe I had to use ONVIF, but I decided to go the way I know.
So, this is how I made a Pan-Tilt control via HTTP requests for Vstarcam C7823WIP in Zoneminder.

Tested on Ubuntu 16.04 and Zoneminder v1.30.0

Zoneminder has some support for PTZ in cameras. The way they can be controlled is described on this page which has many letters but it wasn’t very helpful to me.
Some pieces of advice I found on the blogs and forums which I do not remember already.

The operation logic is following:
– you click an arrow button on ZM WEB-interface
– ZM interface sends POST request to its index.php
– index.php calls ./ajax/control.php
– control.php somehow interacts with /usr/bin/zmcontrol.pl
– zmcontrol.pl executes your Protocol script with command-line arguments that contain information about your action.
– your Protocol script reads arguments and moves the camera

Anyway, here is my path:
1) First, you need to enable controls for your camera in Zoneminder.
From the Console window go to Source link. On the popup window go to Control tab, which looks like this:

001

Then, you need to create your own Control Type. There are some predefined control types in Zoneminder, but if you are reading this – they didn’t work for you, right?
You have to click Edit button in the Control Type row.
You will see Control Capabilities window, where all known control methods are listed. This window has buttons Add, New, Control and Delete at the bottom.

002

Then you have to click Add New Control. There will be the third popup window (such an amazing UI), which has a few tabs.

003

On the tab Main you have to give a name for your control method, to set method type as Remote and to provide a full path to the script (Protocol) which you are going to create and which will accept information about user actions from Zoneminder and send them to the camera via HTTP.
We have no such a script at this stage, but you should decide where you will place it and give him an appropriate permissions because Zoneminder should be able to execute it.

On the Move tab you should check Can Move and Can Move Relative lines.

004

On Pan and Tilt tabs corresponding notches should be checked to enable pan&tilt, also max and min steps should have value 1.

005
006

2) If you are using some different camera or firmware have been changed – it is needed to catch commands from camera’s web interface to see, what kind of commands it sends to move camera.
I used Firebug plugin for Firefox web browser to see what happens when I push pan/tilt buttons on camera’s web interface.
I caught these lines:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
click to move left:
http://CAMERA_IP:PORT/decoder_control.cgi?loginuse=admin&loginpas=888888&command=4&onestep=0&14804454814170.07215148025804918&_=1480445481420
http://CAMERA_IP:PORT/decoder_control.cgi?loginuse=admin&loginpas=888888&command=5&onestep=0&14804454815610.6317436729853317&_=1480445481562
 
click to move right:
http://CAMERA_IP:PORT/decoder_control.cgi?loginuse=admin&loginpas=888888&command=6&onestep=0&14804455785640.812880506517794&_=1480445578567
http://CAMERA_IP:PORT/decoder_control.cgi?loginuse=admin&loginpas=888888&command=7&onestep=0&14804455787130.14883805343257261&_=1480445578713
 
click to move down:
http://CAMERA_IP:PORT/decoder_control.cgi?loginuse=admin&loginpas=888888&command=2&onestep=0&14804456519500.2906950820985412&_=1480445651953
http://CAMERA_IP:PORT/decoder_control.cgi?loginuse=admin&loginpas=888888&command=3&onestep=0&14804456520960.2152557749635573&_=1480445652096
 
click to move up:
http://CAMERA_IP:PORT/decoder_control.cgi?loginuse=admin&loginpas=888888&command=0&onestep=0&14804456975800.2868739222090543&_=1480445697581
http://CAMERA_IP:PORT/decoder_control.cgi?loginuse=admin&loginpas=888888&command=1&onestep=0&14804456977180.6301002319130068&_=1480445697719

I didn’t analyze them deeply, but I presume that web interface sends command to move camera on mouse down and command to stop this movement on mouse up.
I also presume that “digital tail” in those addresses is intended for avoiding browser’s cache.

In these lines I noticed parameter

1
&onestep=0

and after a few tests I simplified HTTP command to something like this:

1
http://CAMERA_IP:PORT/decoder_control.cgi?loginuse=admin&loginpas=888888&command=DIRECTION_COMMAND&onestep=1

Where DIRECTION_COMMAND is one of the digits: 0,2,4,6.
This syntax does not need the stop command. Camera stops automatically after a short movement.

3) Now you’ve got PT(Z) settings for your camera in Zoneminder and you know what must be sent via HTTP to move your camera.
After you enable PTZ in the Source settings – four controls will appear in the Monitor view, but you need to click “Controls” above the monitor picture to reveal them:
007
These controls as ugly as Zoneminder itself, but they work.

As you can conclude from Firebug and documentation page, control buttons send POST requests to Zonminder’s index.php file.
These requests for direction “up” and “down” look like:

1
2
view=request&request=control&id=2&control=moveRelUp&yge=60
view=request&request=control&id=2&control=moveRelDown&yge=40

Now you can create a script, which should be called by Zoneminder and which path you had specified on the step (1) as the Protocol.
This can be either debug script, to check what kind of parameters it gets, or a full functional script according to parameters described here.

But this script even won’t be executed. It won’t be called from Zoneminder.

fuu

As I have found out, the problem is in /usr/bin/zmcontrol.pl script which have to execute different Protocol scripts.
That’s because zmcontrol.pl works with taint checking enabled (#! /usr/bin/perl -T) so it considers passed Protocol path and its parameters as tainted.
You have to change in that file ( about 126 – 128 line)

1
my $output = qx($command);

to

1
2
3
4
# untainting
my ($command2) = 
    ($command =~ m[^(/path/to/script.pl --[[:alnum:]]+=[[:alnum:]]+ --[[:alnum:]]+=[[:alnum:]]+ --[[:alnum:]]+=[[:alnum:]]+)$]g);
my  $output = qx($command2);

or to perform your own taint checking if, for example, you have different number of controls and above regexp doesn’t fit to your command line.
Also you can simply remove “T” switch.

My version of Protocol script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#! /usr/bin/perl
 
use strict;
use utf8;
use LWP::Simple;
 
my $ip          = '192.168.1.2';
my $port        = '81';
my $CamAuth     = "loginuse=admin&loginpas=888888";
 
my %move_commands = (
    moveRelUp       =>  0,
    moveRelDown     =>  2,
    moveRelLeft     =>  4,
    moveRelRight    =>  6,
 
);
 
my %args = ();
 
foreach my $arg (@ARGV){
  my($key, $value) = $arg =~ /^--(\w+)=(\w+)$/;
  $args{$key} = $value;
}
 
do_step( $args{command} );
 
sub do_step{
    my $direction = shift;
 
    my $url = "http://$ip:$port/decoder_control.cgi?$CamAuth&command=$move_commands{$direction}&onestep=1";
    my $content = get($url);
 
}

If you are interested, I suppose you can add the other controls, supported by your camera, the same way.
That’s it.

UPDATE
I faced a problem with my C7823WIP. After restart or reload of Zoneminder, it sometimes can’t connect to camera.
Connection is succeed only after IPCAM reboot.
So I needed a simple way to reboot camera while I am inside of Zoneminder’s interface.
I added the RESET control.
1) At the third image of this post you can see a checkbox “can RESET”. It should be checked and you will see the RESET control under the PAN/TILT controls.
2) You have to change your regexp for untainting in /usr/bin/zmcontrol.pl
I use this:

1
2
my ($command2) = ($command =~ m[^(/path/to/script.pl --[[:alnum:]]+=[[:alnum:]]+ (--[[:alnum:]]+=[[:alnum:]]+ )?--[[:alnum:]]+=[[:alnum:]]+)$]g);
my  $output = qx($command2);

And Protocol script have been changed from 26 line:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
do_step( $args{command} ) if grep{ $args{command} eq $_ } keys %move_commands;
do_reset()                if $args{command} eq 'reset';
 
sub do_step{
  my $direction = shift;
 
  my $url = "http://$ip:$port/decoder_control.cgi?$CamAuth&command=$move_commands{$direction}&onestep=1";
  my $content = get($url);
 
}
 
sub do_reset{
  my $url = "http://$ip:$port/reboot.cgi?next_url=index.htm&$CamAuth";
  my $content = get($url);
}

1 Comment