Watchdog for Vstarcam surveillance camera

I have two VSTARCAM surveillance cameras.
One of them was bought in the spring of 2014 (C7815WIP) and the other in the summer of 2016 (C7823WIP).

The newest camera works almost well so actually it doesn’t need any outer supervisor, but older camera often changes its date/time and some other settings to default values.
So I have written a watchdog, which is run by the cron on my ubuntu server and checks some camera’s settings. If settings are not correct – they are adjusted.

By the way, I opened both cameras to peek on PCB.
Also I needed to detach IR LEDs from C7815WIP because this camera is looking on the street from within the house and gets its own IR reflections from the window glass.
I have to notice significant difference in quality of the PCBs and soldering of these two cameras. I think that Vstarcam have made large improvements on this field since 2014.
Maybe I’ll post a few pictures of PCB later.

Each camera has a web server and can be configured through GET requests.
The main problem in the setting of some specific value through the GET request was to find a digital key (identifier) for needed parameter because many controls are absent on the GUI web interface, but by googling and doing some tests I could find all the keys that I needed to configure my cameras via “command line”.

For example, you can get all of the camera’s parameters by sending request:

1
http://CAMERA_IP:CAMERA_PORT/get_camera_params.cgi?loginuse=admin&loginpas=888888&user=admin&pwd=888888

Where CAMERA_PORT is usually 81 and the string after the question mark is two probable types of login and password keys for Vstarcam.
“admin” and “888888” is default login and password for Vstarcam web interface.

In response your browser will get something like this:

1
2
3
4
5
6
7
8
9
var cameratype=3; var resolution=2; var resolutionsub=0; var resolutionsubsub=1; 
var vbright=128; var vcontrast=128; var vhue=128; var vsaturation=77; 
var OSDEnable=0; var mode=2; var flip=0; var enc_size=2; var enc_framerate=5; 
var enc_keyframe=5; var enc_quant=0; var enc_ratemode=5; var enc_bitrate=1536; 
var enc_main_mode=0; var sub_enc_size=0; var sub_enc_framerate=15; 
var sub_enc_keyframe=15; var sub_enc_quant=0; var sub_enc_ratemode=5; 
var sub_enc_bitrate=512; var sub_sub_enc_size=1; var sub_sub_enc_framerate=10; 
var sub_sub_enc_keyframe=10; var sub_sub_enc_quant=0; var sub_sub_enc_ratemode=5; 
var sub_sub_enc_bitrate=128; var speed=10; var ircut=1; var involume=0; var outvolume=0;

This is a video settings of the camera, which are the most important for me and particularly them I try to check and correct.
Each of these parameters is adjusted the same way:

1
http://CAMERA_IP:CAMERA_PORT/camera_control.cgi?loginuse=admin&loginpas=888888&user=admin&pwd=888888&param=10&value=1

Where param is a digital key for a needed parameter.
Therefore, somehow you have to build a relation between human readable names of these parameters, which are returned by get_camera_params.cgi, and their identifiers which should be used by camera_control.cgi.

I have found needed digital values by sending commands for sequential IDs and determining what was changed in result. (You can also look on the document at the bottom of this page)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1  vbright
2  vcontrast
3  mode
5  flip
6  enc_framerate, enc_keyframe
8  vhue
9  vsaturation
12  sub_enc_keyframe
13  enc_bitrate
14  ircut
15  resolution
17  sub_sub_enc_framerate, sub_sub_enc_keyframe
19  sub_enc_bitrate
20  sub_sub_enc_bitrate
21  enc_ratemode
22  sub_enc_ratemode
23  sub_sub_enc_ratemode

I think that “sub” and “sub_sub” settings are for additional video streams (as I know, C7815WIP has three streams and each of them can be configured independently)
So, to set video framerate to 10fps for smaller video file size you have to perform request:

1
http://CAMERA_IP:CAMERA_PORT/camera_control.cgi?loginuse=admin&loginpas=888888&user=admin&pwd=888888&param=6&value=10

As I’ve read, new Vstarcam cameras support ONVIF, so maybe there is a more decent way to get and to set parameters now, but I didn’t test it.
Anyway, my C7815WIP does not support ONVIF. It should, but I am unable to update firmware to the latest version. So, to have one watchdog script for all my cameras I have to stick with HTTP GET.

In the request you can change the string “get_camera_params.cgi” to “get_params.cgi” and obtain a large list of administrative parameters:

1
2
3
4
var now=1480337174; var tz=-10800; var ntp_enable=0; var ntp_svr="time.nist.gov"; 
var dhcpen=1; var ip="192.168.1.100"; var mask="255.255.255.0"; var gateway="192.168.1.1"; 
var dns1="8.8.8.8"; var dns2="192.168.1.1"; var port=81; var dev2_host=""; var dev2_alias=""; 
var dev2_user=""; var dev2_pwd=""; var dev2_port=0; var dev3_host=""...

All these parameters (except the time, of course) remain stable and do not require correction in my case.
Because among these parameters I correct only time I can not tell if there is a common request to change them all. I only can say that the time has it’s own request syntax:

1
2
3
4
5
6
http://CAMERA_IP:CAMERA_PORT/set_datetime.cgi?next_url=datetime.htm
&loginuse=admin&loginpas=888888&user=admin&pwd=888888
&ntp_svr=YOUR_NTP_SRV
&tz=TIMEZONE
&ntp_enable=IS_NTP_ENABLED
&now=UNIX_TIMESTAMP

YOUR_NTP_SRV must be the address of some NTP server
TIMEZONE must be the shift in seconds from UTC in your local timezone
UNIX_TIMESTAMP is the Unix Time
Here is the full code of my Vstarcam Watchdog.

Perl 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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#! /usr/bin/perl
 
use strict;
use utf8;
use Data::Dumper;
use LWP::Simple;
use Net::Ping;
 
my $port        = '81';
my $CamAuth     = "loginuse=admin&loginpas=888888&user=admin&pwd=888888";
my $p           = Net::Ping->new();
 
# 0 - correction, 1 - unconditional write, 2 - reboot
my $mode = $ARGV[0] // 0;
 
my $cam_params = {
    '192.168.1.100' => {
        vbright         => {id => '1',      default => '128',   current => '' },    # 0~255
        vcontrast       => {id => '2',      default => '128',   current => '' },    # 0~255
        mode            => {id => '3',      default => '2',     current => '' },    # 0:50 hz 1:60 hz 2: Outdoor
        enc_framerate   => {id => '6',      default => '5',     current => '' },    # The main stream frame rate 1-30fps
        vhue            => {id => '9',      default => '128',   current => '' },    # 0~255
        vsaturation     => {id => '8',      default => '77',    current => '' },    # 0~255
        enc_bitrate     => {id => '13',     default => '1536',  current => '' },
    },
    '192.168.1.101' => {
        vbright         => {id => '1',      default => '175',   current => '' },    # 0~255
        vcontrast       => {id => '2',      default => '128',   current => '' },    # 0~255
        mode            => {id => '3',      default => '0',     current => '' },    # 0:50 hz 1:60 hz 2: Outdoor
        enc_framerate   => {id => '6',      default => '5',     current => '' },    # The main stream frame rate 1-30fps
        vhue            => {id => '9',      default => '128',   current => '' },    # 0~255
        vsaturation     => {id => '8',      default => '126',   current => '' },    # 0~255
        enc_bitrate     => {id => '13',     default => '1536',  current => '' },
    },
 
};
 
 
 
my $time_params = {
    now         => {default => '',              current => ''},
    tz          => {default => '-10800',        current => ''},
    ntp_enable  => {default => '0',             current => ''},
    ntp_svr     => {default => 'time.nist.gov', current => ''},
};
 
foreach my $ip (keys %$cam_params){
 
    `echo "camera not reachable $ip" | mail -s "check_ipcam: not reachable." "root\@localhost"`, next unless $p->ping($ip);
 
    if( $mode == 0 ){
 
        get_params($ip, 'time');
 
        print("checking IPCAM $ip - correcting time settings\n" . Dumper($time_params)), correct_cam_time($ip) if(
                abs($$time_params{now}{default} - $$time_params{now}{current}) > 60
            or
                $$time_params{tz}{default} ne $$time_params{tz}{current}
            or
                $$time_params{ntp_enable}{default} ne $$time_params{ntp_enable}{current}
            or
                $$time_params{ntp_svr}{default} ne $$time_params{ntp_svr}{current}
        );
 
        sleep(5);
 
        get_params($ip, 'cam');
 
        foreach my $key (keys %{$$cam_params{$ip}}){
 
            print( "checking IPCAM $ip - correcting $key:  $$cam_params{$ip}{$key}{current} => $$cam_params{$ip}{$key}{default}\n"), correct_cam_param($ip,$key)
                if $$cam_params{$ip}{$key}{default} ne $$cam_params{$ip}{$key}{current};
        }
 
    }
    elsif( $mode == 1 ){
        correct_cam_time($ip);
        correct_cam_param($ip, $_) foreach keys %{$$cam_params{$ip}};
    }
    elsif( $mode == 2 ){
        print "rebooting IPCAM\n";
        my $url = "http://$ip:$port/reboot.cgi?next_url=index.htm&$CamAuth";
        my $content = get($url);
        if( $content ){
            print 'ok';
        }else{
            print 'no response'
        }
 
    }
    else{
        print "uncnown mode $mode\n";
    }
}
 
 
sub get_params{
    my $ip              = shift;
    my $ptype           = shift;
    my $params          = undef;
    my %remote_params   = ();
    die "unknown params type $ptype\n" unless grep{ $ptype eq $_ }qw(time cam);
 
    my $url = do{
        if( $ptype eq 'time' ){
            $params = $time_params;
            "http://$ip:$port/get_params.cgi?$CamAuth"
        }else{
            $params = $cam_params;
            "http://$ip:$port/get_camera_params.cgi?$CamAuth"
        }
    };
 
    my $content = get($url);
    %remote_params = $content =~ /var\s+(.+?)="?(.+?)"?;/g;
 
    if($ptype eq 'time'){
        foreach my $key (keys %$params){
            $$params{$key}{current} = $remote_params{$key};
        }
        $$params{now}{default} = time();
    }
    else{
        foreach my $key (keys %{$$params{$ip}}){
            $$params{$ip}{$key}{current} = $remote_params{$key};
        }
    }
 
}
 
 
 
sub correct_cam_time{
    my $ip  = shift;
    my $url = "http://$ip:$port/set_datetime.cgi?".
				"next_url=datetime.htm&".
				"$CamAuth&ntp_svr=$$time_params{ntp_svr}{default}&".
				"tz=$$time_params{tz}{default}&".
				"ntp_enable=$$time_params{ntp_enable}{default}&now=" . time();
    my $content = get($url);
    sleep(10);
}
 
sub correct_cam_param{
    my $ip  = shift;
    my $key = shift;
    my $url = "http://$ip:$port/camera_control.cgi?$CamAuth&param=$$cam_params{$ip}{$key}{id}&value=$$cam_params{$ip}{$key}{default}";
    my $content = get($url);
    #print $content;
    if( $content !~ 'var result="ok";'){
      print "correct param failed!\nparam: $key; response: $content\n ";
    }
    sleep(5);
}
 
=pod
available bitrates
"128", "256", "384", "512", "640","768","896",
"1024","1152","1280","1408","1536","1664","1792","1920",
"2048","2176","2304","2432","2560","2688","2816","2944",
"3072","3200","3328","3456","3584","3712","3840","3968","4096"
=cut

Backticks in the line #49 can be replaced by simple print statement if you need only mail to root@localhost, because all output from cron scripts is sent to root@localhost by default if any MTA installed on the system.

My tasks in cron for this watchdog:

1
2
3
4
# m h  dom mon dow   command
*/15 * * * * /opt/myscripts/check_ipcam.pl 0
10 0,12 * * * /opt/myscripts/check_ipcam.pl 1
5 0 * * 7 /opt/myscripts/check_ipcam.pl 2

That means that all cameras are checked every 15 minutes.
At 12:10 and 00:10 all setting is writing to cameras unconditionally.
At 00:05 each Sunday all cameras are rebooted.

On the PC I use Vstarcam’s “IP Camera super client” version 1.1.4.557 to watch live video and to record stream for the last few days.
I am stick with this specific version because other versions have very buggy ability to change time and bitrate of connected cameras which creates a lot of problems.
They often set wrong time values and default (25 fps) framerate, though I had set 15fps in settings.
Furthermore, earlier versions of “Super Client” sustained those wrong values so I couldn’t change them by other separate software while the Super Client is running.

And one more thing. Both cameras are blackboxes actually. What is in their firmware is unknown.
After power on they are establishing connections not only to NTP servers but also to some unknown, unnamed servers in Amazon cloud.
So I’ve blocked them access to the Internet and until their firmware is unable to change the MAC address – my block will work.

Interestingly enough, my C7815WIP also has opened telnet port where I can login as root with password 123456 (I have found it on the Internet)

IPcam shell
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
(none) login: root
Password:
Welcome to HiLinux.
# ps -p $$
ps: invalid option -- p
BusyBox v1.16.1 (2014-03-07 11:46:31 CST) multi-call binary.
 
# ls -l
drwxr-xr-x    2 root     root          1530 Apr 10  2014 bin
drwxr-xr-x    2 1010     1010             3 Jul  9  2013 boot
drwxrwxrwt    5 root     root          3320 Jan  1  1970 dev
drwxr-xr-x    5 1000     1000           224 Mar 21  2014 etc
drwxr-xr-x    2 1010     1010             3 Jul  9  2013 home
lrwxrwxrwx    1 root     root             9 Mar 21  2014 init -> sbin/init
drwxr-xr-x    3 1014     1014           844 Apr 10  2014 lib
lrwxrwxrwx    1 root     root            11 Mar 21  2014 linuxrc -> bin/busybox
drwxr-xr-x    2 1010     1010             3 Jul  9  2013 lost+found
-rw-r--r--    1 1010     1010          1341 Apr 21  2011 mkimg.rootfs
-rw-r--r--    1 1010     1010           431 Apr 21  2011 mknod_console
drwxr-xr-x    6 1010     1010            63 Mar 24  2014 mnt
drwxr-xr-x    2 1010     1010             3 Jul  9  2013 nfsroot
drwxr-xr-x    2 1010     1010             3 Jul  9  2013 opt
dr-xr-xr-x   57 root     root             0 Jan  1  1970 proc
drwxr-xr-x    2 1010     1010            31 Jul  9  2013 root
drwxr-xr-x    2 root     root           893 Mar  7  2014 sbin
drwxr-xr-x    2 1010     1010             3 Jul  9  2013 share
drwxr-xr-x   11 root     root             0 Jan  1  1970 sys
drwxr-xr-x    7 root     root             0 Jan  1  1970 system
drwxrwxrwt    2 root     root           240 Nov 27 09:10 tmp
drwxr-xr-x    6 root     root            62 Mar  7  2014 usr
drwxr-xr-x    4 1010     1010            37 Apr  4  2014 var

UPDATE
After a few months of usage I noticed that weekly reboots and daily unconditional writes do not increase the stability of cameras. Especially C7823WIP.
So I have rescinded them.

Sometimes it is impossible to get a stream out of C7823WIP without reboot. Fortunately, such condition on my firmware can be recognized by values of camera’s parameters. They are all ‘undefined’.
So, I have created a separate reboot procedure and added a checking of camera’s parameters right after they had been read (lines 54,68)

1
2
    # special case - cam is hanging - all params read as undef
    reboot_ipcam($ip), next IP unless grep{ defined $_ } map{ $$_{current} }values %{$$cam_params{$ip}};
1
2
3
4
5
6
7
8
9
10
11
12
sub reboot_ipcam{
    my $ip = shift;
 
    print "rebooting IPCAM $ip\n";
    my $url = "http://$ip:$port/reboot.cgi?next_url=index.htm&$CamAuth";
    my $content = get($url);
    if( $content ){
        print 'ok';
    }else{
        print 'no response'
    }
}

UPDATE
Occasionally I ran across a document on the internet which describes HTTP interface to Vstarcam cameras.
It is written partially in English, partially in Chinese, but it contains some interesting descriptions.
In this document you can find digital keys for camera’s parameters but not all of them. Part of the keys are absent because it is proposed to change these parameters using different interface: set_media.cgi, though actually both methods (camera_control.cgi and set_media.cgi) works.
TIP-Camera-CGI.pdf