Hikvision IR control

It would be wonderful if the hikvision component could also control the night and day mode of a camera. My driveway lights keep sending my camera into day mode at night. It would be great to leave the camera in manual mode and automate its IR control via HA.

Only way I’ve found to do it so far is with some third party perl code posted on cam-it to send the camera a new schedule each day.

#! /usr/bin/perl


# Name: HikSunLoop.pl

# Original Author: Ziegeid
# Description: Change the setting on the HikVision ds-2cd2032-i Camera at sunrise and sunset
# Camera: HikVision ds-2cd2032-i
# Firmware Version: V5.0.2 130805


# Version 2.1
# Date: 9/12/2013
#     Updated by: dalepa
#         Description: Updated to sleep until the next event instead of using an external scheduler
#
#        Usage:  perl "PATH\hiksunloop.pl"
#                or             
#                wperl      "PATH\hiksunloop.pl"   (run in background)
#

# Step 1. Install ActivePerl:  [url]http://www.activestate.com/activeperl/downloads[/url]
# Step 2. From the dos prompt, run ppm install datetime-Event-sunrise
# Step 3. Edit the attached hiksunlook.pl script Configuration section #1 to #4  (notepad++ is great BTW)
# Step 4. Change CONFIGURATION SECTION below with your setting
# Step 5: Test Manually  Exmaple: D:\Perl64\bin\perl.exe "X:\Google Drive\Code\Perl\hiksunloop.pl"  (CTRL-C to STOP)
# Step 6: Schedule to run in Windows Scheduler 
#    Example Windows Scheduler Settings
#        Actions
#            Program/script    # Your path to wperl
#                D:\Perl64\bin\wperl.exe
#            Add Arguments    #path to script
#                "X:\Google Drive\Code\Perl\hiksunloop.pl"
#        Triggers On a schedule
#            One time at 12pm on 09/12/2013 - After triggered repeat every 12 hours indefinitely
#        Settings
#            If the Task is already running, then the following rule applies
#                Do Not start a new instance
#


# {{{ use

use strict;
use warnings;
use List::Util qw[min max];


use Time::Local;
use POSIX qw(ctime difftime mktime);
use HTTP::Date;
 
use Data::Dumper;
use DateTime::Event::Sunrise;
use DateTime;
use LWP::UserAgent;
use MIME::Base64;
use JSON;

# }}}

# {{{ Configuration section



#1 only hardcode Long/Lat if the [url]http://freegeoip.net/json/[/url] can't autodiscover it.
my $latitude = '0';   #29.5723
my $longitude = '0';  #-95.136

#2 Camera username/password
my $username = 'admin';
my $password = '12345';

#3 Add your cameras with your settings....

my %cameras = (
               # Camera #1
               '192.168.15.100:100' =>
               { night => 
                 {
                  NoiseReduceExt => 100,
                  WDR => 0,
                  # IrcutFilterType => 'night',
                 },
                 day => 
                 {
                  NoiseReduceExt => 0,
                  WDR => 35,
                  # IrcutFilterType => 'day',
                 },
               },         



               # Add more cameras here...
               # Camera #2
               
                 
              );

# }}}

# {{{ Map camera actions to XML requests

my %actions = (
               NoiseReduceExt  => q@<?xml version="1.0" encoding="UTF-8"?><NoiseReduceExt xmlns="urn:selfextension:psiaext-ver10-xsd" version="1.0"><mode>general</mode><GeneralMode><generalLevel>%s</generalLevel></GeneralMode></NoiseReduceExt>@,
               WDR             => q@<?xml version="1.0" encoding="UTF-8"?><WDR><enabled>true</enabled><WDRLevel>%s</WDRLevel><WDRContrastLevel>0</WDRContrastLevel></WDR>@,
               brightnessLevel => q@<?xml version="1.0" encoding="UTF-8"?><Color><brightnessLevel>%s</brightnessLevel></Color>@,
               contrastLevel   => q@<?xml version="1.0" encoding="UTF-8"?><Color><contrastLevel>%s</contrastLevel></Color>@,
               saturationLevel => q@<?xml version="1.0" encoding="UTF-8"?><Color><saturationLevel>%s</saturationLevel></Color>@,
               hueLevel        => q@<?xml version="1.0" encoding="UTF-8"?><Color><hueLevel>%s</hueLevel></Color>@,
               SharpnessLevel  => q@<?xml version="1.0" encoding="UTF-8"?><Sharpness xmlns="urn:selfextension:psiaext-ver10-xsd" version="1.0"><SharpnessLevel>%s</SharpnessLevel></Sharpness>@,

               # Can be one of: day night auto
               # nightToDayFilterTime is `Switch Time' in the GUI
               IrcutFilterType => q@<?xml version="1.0" encoding="UTF-8"?><IrcutFilterExt><IrcutFilterType>%s</IrcutFilterType><nightToDayFilterLevel>normal</nightToDayFilterLevel><nightToDayFilterTime>10</nightToDayFilterTime></IrcutFilterExt>@,

               # auto1 => AWB1
               # locked => Locked WB
               # incandescentlight => Incandescent Lamp
               # warmlight => Warm Light Lamp
               # naturallight => Natural Light
               # daylightLamp => Fluorescent Light
               WhiteBlanceStyle => q@<?xml version="1.0" encoding="UTF-8"?><WhiteBlance xmlns="urn:selfextension:psiaext-ver10-xsd" version="1.0"><WhiteBlanceStyle>%s</WhiteBlanceStyle></WhiteBlance>@,

               # Set 1 or more Manual White Balance settings and switch between them
               WhiteBlanceStyleManual1 => q@<?xml version="1.0" encoding="UTF-8"?><WhiteBlance xmlns="urn:selfextension:psiaext-ver10-xsd" version="1.0"><enabled>true</enabled><WhiteBlanceStyle>manual</WhiteBlanceStyle><WhiteBlanceRed>0</WhiteBlanceRed><WhiteBlanceBlue>0</WhiteBlanceBlue></WhiteBlance>@,
               WhiteBlanceStyleManual2 => q@<?xml version="1.0" encoding="UTF-8"?><WhiteBlance xmlns="urn:selfextension:psiaext-ver10-xsd" version="1.0"><enabled>true</enabled><WhiteBlanceStyle>manual</WhiteBlanceStyle><WhiteBlanceRed>20</WhiteBlanceRed><WhiteBlanceBlue>20</WhiteBlanceBlue></WhiteBlance>@,
              );

# }}}

my $PROGRAM = ($0 =~ /^(.*\/){0,1}(.+)/)[1];

my $DEBUG = 0;



# Try to autoset lat/long
if ($latitude == 0 && $longitude == 0) {
  ($latitude, $longitude) = get_location();
  if ($latitude == 0 && $longitude == 0) {
    die "$PROGRAM: unable to auto-discover location.\nPlease set \$latitude and \$longitude to non-zero values.\n\t";
  }
  print "Auto-detected latitude = $latitude and longitude = $longitude .\nIf these are not correct, or close, please edit the script.\n\n";
}

# Create base objects
my $LocalTZ = DateTime::TimeZone->new(name => 'local');
my $dt = DateTime->now(time_zone => $LocalTZ);


# Find sunrise/sunset for current location.
my $sunrise = DateTime::Event::Sunrise->sunrise(
                                                longitude => $longitude,
                                                latitude => $latitude,
                                                altitude => '-0.833',
                                                iteration => '1'
                                               );

my $sunset = DateTime::Event::Sunrise->sunset(
                                              longitude => $longitude,
                                              latitude => $latitude,
                                              altitude => '-0.833',
                                              iteration => '1'
                                             );

# Find the next sunrise/sunset.

        my $next_sunrise;
        my $next_sunset;
        my $day_set;
        my $daytime;
        my $nowtime;
        my $sunrisetime;
        my $sunsettime;
        my $sectillsunrise;
        my $sectillsunset;
        my $sleepseconds;

        

    while (1) 
    {
    
        $dt = DateTime->now(time_zone => $LocalTZ);
        $next_sunrise = $sunrise->next($dt);
        $next_sunset = $sunset->next($dt);

        # Figure out if it's currently day or night
        $day_set = DateTime::SpanSet->from_sets(start_set => $sunrise, end_set => $sunset);
        $daytime = $day_set->contains($dt) ? 'day' : 'night';

        # Print some data
        printf("%s: %s\n\n", $dt->strftime("%Y-%m-%d %H:%M"), $daytime);
        printf "Next sunset: %s\n", $next_sunset->strftime("%a, %d %b %Y %H:%M:%S");
        printf "Next sunrise: %s\n", $next_sunrise->strftime("%a, %d %b %Y %H:%M:%S");

        do_action($daytime);   #do it now
        
    
        # Calculate the time to sleep until the next sunrise.
        
        $nowtime = time;
        $sunrisetime = str2time($next_sunrise->strftime("%a, %d %b %Y %H:%M:%S"));
        $sunsettime = str2time($next_sunset->strftime("%a, %d %b %Y %H:%M:%S"));

        $sectillsunrise = $sunrisetime-$nowtime;
        $sectillsunset = $sunsettime-$nowtime;

        $sleepseconds = min ($sectillsunrise,$sectillsunset);

        printf "Hours till sunrise %d\n",$sectillsunrise/60/60;
        printf "Hours till sunset %d\n",$sectillsunset/60/60;
        printf "Sleeping %f Hours \n",$sleepseconds/60/60;
        
        sleep($sleepseconds+60);  # sleep until the next sunrise/sunset
    
        # wake up, the light has changed...  sunset or sunrise
        
    }



# {{{ sub do_action

sub do_action {
  my $mode = shift;

  my $ua = LWP::UserAgent->new;
  $ua->timeout(10);

  my $auth = encode_base64("$username:$password");
  $ua->default_header('Cookie' => "userInfo80=$auth; tabSystem_curTab=0; menu_onemenu=110; tabAudioAndVideo_curTab=0; menu_twomenu=1_3; tabVideoSettings_curTab=0");
  $ua->default_header('Authorization' => "Basic $auth");
  $ua->default_header('Content-Type' => 'application/x-www-form-urlencoded');
  $ua->default_header('X-Requested-With' => 'XMLHttpRequest');

  foreach my $camera (keys(%cameras)) {
    print "\n$camera => $mode:\n";

    while (my ($action, $value) = each($cameras{$camera}{$mode})) {
      my $base = "/PSIA/Custom/SelfExt/Image/channels/1/";
      
      my $xml = sprintf($actions{$action}, $value);

      my $xml_action = undef;
      if ($xml =~ m/^<[^>]+><(\w+)\W/) {
        $xml_action = $1;
      } else {
        die "$PROGRAM: Unable to extact URL action portion from XML:\n$xml\n\t";
      }

      my $url = sprintf("http://%s%s%s", $camera, $base, $xml_action);


      print "\t$action => $value, ";
      if ($DEBUG >= 1) {
        print "URL: $url, ";
      }
      
      if ($DEBUG == 0 or $DEBUG >= 2) {
        my $response = $ua->put($url, Content => $xml);
        
        ### A response looks like this:
        # <?xml version="1.0"?>
        # <ResponseStatus version="1.0" xmlns="urn:psialliance-org">
        # <requestURL>/PSIA/Custom/SelfExt/Image/channels/1/WDR</requestURL>
        # <statusCode>1</statusCode>
        # <statusString>OK</statusString>
        # </ResponseStatus>
        
        if ($response->is_success && 
            $response->decoded_content =~ m@\<statusCode\>1\</statusCode\>@ &&
            $response->decoded_content =~ m@\<statusString\>OK\</statusString\>@) {
          print "success.\n";
        } else {
          die "$PROGRAM: received an error response from camera:\n$response->status_line, $response->decoded_content\n\t";
        }
      } else {
        print "DEBUG no action taken.\n";
      }
    }
  }
}

# }}}


# {{{ sub get_location

sub get_location {
  my $latitude = 0;
  my $longitude = 0;

  my $ua = LWP::UserAgent->new;
  $ua->timeout(10);
  my $response = $ua->get('[url]http://freegeoip.net/json'[/url]);

  if ($response->is_success) {
    my $json = decode_json($response->decoded_content);
    $latitude = $json->{latitude};
    $longitude = $json->{longitude};
  }
    
  return ($latitude, $longitude);
}

# }}}